Zapobiegaj podwójnemu przesyłaniu formularzy w jQuery

170

Mam formularz, którego przetworzenie przez serwer zajmuje trochę czasu. Muszę się upewnić, że użytkownik czeka i nie próbuje ponownie przesłać formularza, klikając ponownie przycisk. Próbowałem użyć następującego kodu jQuery:

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

Kiedy próbuję tego w Firefoksie, wszystko zostaje wyłączone, ale formularz nie jest przesyłany z żadnymi danymi POST, które powinien zawierać. Nie mogę użyć jQuery do przesłania formularza, ponieważ potrzebuję przycisku do przesłania wraz z formularzem, ponieważ istnieje wiele przycisków przesyłania i określam, który został użyty, przez którą wartość jest uwzględniona w POST. Potrzebuję przesłania formularza tak, jak to zwykle bywa i muszę wszystko wyłączyć zaraz po tym.

Dzięki!

Adam
źródło
Dziękuję wszystkim za pomoc!
Adam

Odpowiedzi:

315

Aktualizacja w 2018 r . : Właśnie dostałem kilka punktów za tę starą odpowiedź i chciałem tylko dodać, że najlepszym rozwiązaniem byłoby uczynienie operacji idempotentną, aby zduplikowane zgłoszenia były nieszkodliwe.

Np. Jeśli formularz tworzy zamówienie, umieść w formularzu unikalny identyfikator. Gdy serwer po raz pierwszy zobaczy żądanie utworzenia zamówienia o tym identyfikatorze, powinien je utworzyć i odpowiedzieć „sukces”. Kolejne zgłoszenia również powinny odpowiadać „sukces” (w przypadku, gdy klient nie otrzymał pierwszej odpowiedzi), ale nie powinny niczego zmieniać.

Duplikaty powinny być wykrywane przez kontrolę unikalności w bazie danych, aby zapobiec sytuacjom wyścigu.


Myślę, że Twoim problemem jest ta linia:

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

Wyłączasz WSZYSTKIE dane wejściowe, w tym, jak sądzę, te, których dane ma przesłać formularz.

Aby wyłączyć tylko przyciskiem przedstawienia (S), to mogłaby to zrobić:

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

Nie sądzę jednak, aby IE przesłał formularz, jeśli nawet te przyciski są wyłączone. Proponuję inne podejście.

Wtyczka jQuery do rozwiązania tego problemu

Właśnie rozwiązaliśmy ten problem za pomocą następującego kodu. Sztuczka polega na tym, że użycie jQuery data()do oznaczenia formularza jako już przesłanego lub nie. W ten sposób nie musimy majstrować przy przyciskach przesyłania, co wkurza 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;
};

Użyj tego w ten sposób:

$('form').preventDoubleSubmission();

Jeśli istnieją formularze AJAX, które powinny być przesyłane wiele razy podczas ładowania strony, możesz nadać im klasę wskazującą na to, a następnie wykluczyć je ze swojego selektora w następujący sposób:

$('form:not(.js-allow-double-submission)').preventDoubleSubmission();
Nathan Long
źródło
2
Tylko wskaźnik - czy nie byłoby lepiej zapisać w pamięci podręcznej $(this)do a var that = $(this)?
Stuart.Sklinar
5
@ Stuart.Sklinar - dobry pomysł. Użyłem $formjednak - 'form', ponieważ jest bardziej opisowy i wiodący, $ponieważ według mojej konwencji oznacza to, że jest to obiekt jQuery.
Nathan Long,
21
Korzystając z jQuery, potwierdź dyskretną walidację, lepiej sprawdź, czy formularz jest prawidłowy. if ($ (this) .valid)) {$ form.data ('submitted', true);}
cck
1
@cck Awesome, użyłem tego. Miałeś dodatkowy paren zamykający, który usunąłem: if ($ (this) .valid) {$ form.data ('submitted', true);
Brian MacKay,
1
@BrianMacKay jeśli formularz jest nieprawidłowy i naciśnięto przycisk Prześlij, ta wtyczka jquery oznacza przesłany atrybut jako prawdziwy, ale dyskretna walidacja zapobiega przesłaniu z powodu błędów walidacji. Po poprawieniu błędów nie można wysłać formularza, ponieważ przesłany atrybut jest prawdziwy!
cck
44

Podejście czasowe jest nieprawidłowe - skąd wiesz, jak długo potrwa działanie w przeglądarce klienta?

Jak to zrobić

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

Gdy formularz zostanie przesłany, wyłączy wszystkie znajdujące się w nim przyciski przesyłania.

Pamiętaj, że w przeglądarce Firefox po wyłączeniu przycisku ten stan zostanie zapamiętany, gdy wrócisz do historii. Aby temu zapobiec, musisz na przykład włączyć przyciski podczas ładowania strony.

Ctrl + C
źródło
2
+1 ze względu na wskazówkę dotyczącą Firefoksa, ale czy jest jakaś inna opcja oprócz włączania przycisków podczas ładowania strony?
Raphael Isidro
1
To najczystsza droga. Próbowałem wyłączyć przycisk przesyłania za pomocą modułu obsługi zdarzeń kliknięcia, ale moje podejście powodowało problemy z Chrome. To rozwiązanie działa pięknie.
Anthony do
1
Zachowaj ostrożność przy takim podejściu, jeśli masz wartość na przycisku przesyłania i jej potrzebujesz, formularz jej nie prześle.
Fabien Ménager
Zgodnie z najnowszą dokumentacją jQuery powinieneś używać .prop zamiast .attr. Odniesienie: api.jquery.com/prop/#entry-longdesc-1 "Metoda .prop () powinna być używana do wyłączania i sprawdzania metody zamiast .attr ()."
J Plana
19

Myślę, że odpowiedzią Nathana Longa jest droga. W moim przypadku używam walidacji po stronie klienta, więc właśnie dodałem warunek, że formularz jest ważny.

EDYCJA : Jeśli ta opcja nie zostanie dodana, użytkownik nigdy nie będzie mógł przesłać formularza, jeśli walidacja po stronie klienta napotka błąd.

        // 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
źródło
9

event.timeStampnie działa w przeglądarce Firefox. Zwracanie false jest niestandardowe, powinieneś zadzwonić event.preventDefault(). A skoro już o tym mowa , zawsze używaj nawiasów klamrowych z konstrukcją kontrolną .

Podsumowując wszystkie poprzednie odpowiedzi, oto wtyczka, która wykonuje zadanie i działa w różnych przeglądarkach.

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

Aby rozwiązać problem Kern3l, metoda pomiaru czasu działa u mnie po prostu dlatego, że próbujemy zatrzymać dwukrotne kliknięcie przycisku przesyłania. Jeśli masz bardzo długi czas odpowiedzi na zgłoszenie, polecam zastąpienie przycisku wysyłania lub formularza spinnerem.

Całkowite blokowanie kolejnych wysyłek formularza, jak to ma miejsce w większości powyższych przykładów, ma jeden zły efekt uboczny: jeśli wystąpi awaria sieci i chcą spróbować przesłać ponownie, nie byliby w stanie tego zrobić i utraciliby wprowadzone zmiany. zrobiony. To zdecydowanie rozzłościłoby użytkownika.

Robin Daugherty
źródło
2
Masz rację. Możesz to zrobić w drugą stronę - natychmiast wyłącz, a po dłuższym czasie włącz ponownie. Zapobiega podwójnemu kliknięciu, umożliwia ponowne przesłanie, ale nadal umożliwia nieprawidłowe ponowne przesłanie opóźnionym użytkownikom.
Ctrl-C
8

Proszę, sprawdź wtyczkę jquery-safeform .

Przykład użycia:

$('.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
źródło
4

... ale formularz nie jest przesyłany z żadnymi danymi POST, który ma zawierać.

Poprawny. Wyłączone nazwy / wartości elementów formularza nie będą wysyłane na serwer. Powinieneś ustawić je jako elementy tylko do odczytu .

Ponadto zakotwiczenia nie można w ten sposób wyłączyć. Będziesz musiał albo usunąć ich HREF (niezalecane), albo zapobiec ich domyślnemu zachowaniu (lepszy sposób), np .:

<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
źródło
@Adam, tak, wszystkie dołączone moduły obsługi kliknięć zostaną uruchomione. Myślałem, że po prostu chcesz powstrzymać ich przed modyfikacją?
karim79
@ karim79 Nie obchodzi mnie, czy programy obsługi kliknięć zostaną uruchomione, chcę się tylko upewnić, że formularz nie zostanie ponownie przesłany przez użytkownika, który kliknie inny przycisk przesyłania.
Adam
@Adam - nie, to się nie stanie. I mój przykład, dodałem, $('input[type=submit]').attr("disabled", "disabled");ale moim ulubionym sposobem jest umieszczenie onclick="this.disabled = 'disabled'"w przycisku onclick przesyłania.
karim79
@ karim79 Próbowałem, ale przycisk przesyłania, który klikam, nie jest uwzględniany w POST. Potrzebuję uwzględnienia go w POST, aby określić, który przycisk został kliknięty
Adam
Wiem, że właśnie cofnąłeś licytację zdarzenia click dla „a”, ale dlaczego nie użyć po prostu $ („a”). Click (function (e) {e.preventDefault (); // or return false;}); ?
Fabio
4

Istnieje możliwość ulepszenia podejścia Nathana Longa . Możesz zastąpić logikę wykrywania już przesłanego formularza na następującą:

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
Michaił Fiadosenka
źródło
1
Dlaczego, u licha, miałbyś używać timera do tego podejścia?
Bobort
4

Kod Nathana, ale dla wtyczki jQuery Validate

Jeśli zdarzy ci się użyć wtyczki jQuery Validate, mają już zaimplementowaną procedurę obsługi przesyłania, a w takim przypadku nie ma powodu, aby implementować więcej niż jedną. Kod:

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

Możesz łatwo rozszerzyć go w ramach } else {-block, aby wyłączyć wejścia i / lub przycisk przesyłania.

Twoje zdrowie

Alph.Dev
źródło
2

Skończyło się na tym, że wykorzystałem pomysły z tego postu, aby znaleźć rozwiązanie, które jest dość podobne do wersji 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;
  });   
};

Używanie w ten sposób:

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

Okazało się, że rozwiązania, które nie obejmowały jakiegoś limitu czasu, a jedynie wyłączono przesyłanie lub wyłączone elementy formularza, powodowały problemy, ponieważ po uruchomieniu blokady nie można przesłać ponownie, dopóki nie odświeżysz strony. To sprawia mi pewne problemy podczas robienia rzeczy Ajax.

Można to prawdopodobnie trochę upiększyć, ponieważ nie jest to takie wyszukane.

jacklin
źródło
2

Jeśli używasz AJAX do wysyłania formularza, ustawianie async: falsepowinno zapobiegać dodatkowym przesłaniom przed wyczyszczeniem formularza:

$("#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
źródło
3
Ustawienie <code> asnyc: true </code> jest złym podejściem, ponieważ zawiesza przeglądarkę do czasu zakończenia żądania, co bije istotę AJAX
Edwin M
2

Zmodyfikowane nieco rozwiązanie Nathana dla Bootstrap 3. Spowoduje to ustawienie wczytywania tekstu na przycisku wysyłania. Ponadto wygaśnie po 30 sekundach i pozwoli na ponowne przesłanie formularza.

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
źródło
2

Użyj dwóch przycisków przesyłania.

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

Oraz jQuery:

$("#sub").click(function(){
  $(this).val("Please wait..");
  $(this).attr("disabled","disabled");
  $("#sub2").click();
});
izmir_LEE
źródło
2

Użyj prostego licznika podczas przesyłania.

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

I wywołaj monitor()funkcję po przesłaniu formularza.

    <form action="/someAction.go" onsubmit="return monitor();" method="POST">
        ....
        <input type="submit" value="Save Data">
    </form>
Panie Mak
źródło
1

Miałem podobne problemy, a moje rozwiązania są następujące.

Jeśli nie masz żadnej weryfikacji po stronie klienta, możesz po prostu użyć metody jquery one (), jak opisano tutaj.

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

Spowoduje to wyłączenie programu obsługi po jego wywołaniu.

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

Jeśli przeprowadzasz walidację po stronie klienta, tak jak ja, wtedy jest to nieco trudniejsze. Powyższy przykład nie pozwala na ponowne przesłanie po nieudanej weryfikacji. Zamiast tego wypróbuj to podejście

$("#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
źródło
1

Możesz zatrzymać drugie przesyłanie w ten sposób

$("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
źródło
0

Moje rozwiązanie:

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

źródło
0

W moim przypadku przesłanie formularza przy przesłaniu zawierało kod weryfikacyjny, więc zwiększam odpowiedź Nathana Longa, w tym punkt kontrolny przy przesłaniu

$.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
źródło
0

Zmień przycisk przesyłania:

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

Z normalnym przyciskiem:

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

Następnie użyj funkcji kliknięcia:

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

I pamiętaj, aby ponownie włączyć przycisk, gdy jest to konieczne:

$('#submitButtonId').prop('disabled', false);
Dani
źródło
0

Nie mogę uwierzyć w dobrą staromodną sztuczkę CSS dotyczącą zdarzeń wskaźnikowych: żaden nie został jeszcze wspomniany. Miałem ten sam problem, dodając wyłączony atrybut, ale to nie jest wysyłane z powrotem. Wypróbuj poniższe i zamień #SubmitButton na identyfikator swojego przycisku przesyłania.

$(document).on('click', '#SubmitButton', function () {
    $(this).css('pointer-events', 'none');
})
Tom McDonough
źródło
Nie zatrzymuje to przesyłania formularzy, gdy użytkownik naciśnie Enterklawisz.
zreformowany
0

Dlaczego nie tylko to - spowoduje to przesłanie formularza, ale również wyłączy przycisk wysyłania,

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

Ponadto, jeśli używasz jQuery Validate, możesz umieścić te dwie linie poniżej if ($('#myForm').valid()).

gen b.
źródło
0

ten kod wyświetli ładowanie na etykiecie przycisku i ustaw przycisk na

wyłącz stan, a następnie po przetworzeniu włącz ponownie i przywróć oryginalny tekst przycisku **

$(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
źródło
-1

Bardzo podobny problem rozwiązałem używając:

$("#my_form").submit(function(){
    $('input[type=submit]').click(function(event){
        event.preventDefault();
    });
});
Nolan
źródło