Impedir o envio duplo de formulários no jQuery

170

Eu tenho um formulário que demora um pouco para o servidor processar. Preciso garantir que o usuário aguarde e não tente reenviar o formulário clicando no botão novamente. Tentei usar o seguinte código 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 tento isso no Firefox, tudo fica desativado, mas o formulário não é enviado com nenhum dado do POST que ele deve incluir. Não posso usar o jQuery para enviar o formulário, pois preciso que o botão seja enviado com o formulário, pois existem vários botões de envio e determino qual foi usado pelo qual o valor de alguém está incluído no POST. Preciso que o formulário seja enviado como normalmente é e preciso desativar tudo logo após isso acontecer.

Obrigado!

Adão
fonte
Obrigado a todos por sua ajuda!
1326 Adam

Respostas:

315

Atualização em 2018 : recebi alguns pontos para esta resposta antiga e só queria acrescentar que a melhor solução seria tornar a operação idempotente para que envios duplicados sejam inofensivos.

Por exemplo, se o formulário criar um pedido, coloque um ID exclusivo no formulário. A primeira vez que o servidor vê uma solicitação de criação de pedido com esse ID, deve criá-la e responder "sucesso". Os envios subsequentes também devem responder "com êxito" (caso o cliente não tenha recebido a primeira resposta), mas não devem mudar nada.

As duplicatas devem ser detectadas por meio de uma verificação de exclusividade no banco de dados para evitar condições de corrida.


Eu acho que seu problema é esta linha:

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

Você está desativando TODAS as entradas, incluindo, eu acho, aquelas cujos dados o formulário deve enviar.

Para desativar apenas os botões de envio, você pode fazer o seguinte:

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

No entanto, acho que o IE não enviará o formulário se esses botões estiverem desativados. Eu sugeriria uma abordagem diferente.

Um plugin jQuery para resolvê-lo

Acabamos de resolver esse problema com o seguinte código. O truque aqui é usar o jQuery data()para marcar o formulário como já enviado ou não. Dessa forma, não precisamos mexer nos botões de envio, o que assusta o 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;
};

Use-o assim:

$('form').preventDoubleSubmission();

Se houver formulários AJAX que devam ser autorizados a enviar várias vezes por carregamento de página, você poderá atribuir a eles uma classe indicando isso e excluí-los do seletor da seguinte maneira:

$('form:not(.js-allow-double-submission)').preventDoubleSubmission();
Nathan Long
fonte
2
Apenas um ponteiro - não seria melhor armazenar em cache o $(this)para a var that = $(this)?
Stuart.Sklinar
5
@ Stuart.Sklinar - boa ideia. Eu usei $formembora - 'form' porque é mais descritivo, e o principal $porque pela minha convenção, isso significa que é um objeto jQuery.
Nathan Long
21
Ao usar o jquery, valide a validação discreta melhor para verificar se o formulário é válido. if ($ (this) .valid)) {$ form.data ('enviado', true);}
cck
1
@cck Awesome, eu usei isso. Você tinha um ponto extra de fechamento que eu removi: if ($ (this) .valid) {$ form.data ('submit', true);
quer
1
@BrianMacKay se o formulário não for válido e o botão enviar for pressionado, este plugin jquery marca o atributo enviado como verdadeiro, mas a validação discreta impede o envio devido a erros de validação. Depois de corrigir os erros, não é possível enviar o formulário porque o atributo enviado é verdadeiro!
Cck
44

A abordagem de tempo está incorreta - como você sabe quanto tempo a ação levará no navegador do cliente?

Como fazer isso

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

Quando o formulário é enviado, todos os botões de envio são desativados.

Lembre-se, no Firefox, quando você desabilita um botão, esse estado será lembrado quando você voltar ao histórico. Para impedir que você precise ativar os botões no carregamento da página, por exemplo.

Ctrl-C
fonte
2
+1 por causa da dica do Firefox, mas existe outra opção além de ativar os botões no carregamento da página?
Raphael Isidro
1
Este é o caminho mais limpo a seguir. Eu estava tentando desativar o botão enviar com um manipulador de eventos de clique, mas minha abordagem estava causando problemas com o chrome. Esta solução funciona lindamente.
Anthony Até
1
Tenha muito cuidado com essa abordagem, se você tiver um valor no botão enviar e precisar, o formulário não o enviará.
Fabien Ménager 20/01
De acordo com a documentação mais recente do jQuery, você deve usar .prop em vez de .attr. Referência: api.jquery.com/prop/#entry-longdesc-1 "O método .prop () deve ser usado para definir desativado e verificado em vez do método .attr ()."
J Plana
19

Acho que a resposta de Nathan Long é o caminho a percorrer. Para mim, estou usando a validação do lado do cliente, então acabei de adicionar uma condição para que o formulário seja válido.

EDIT : Se isso não for adicionado, o usuário nunca poderá enviar o formulário se a validação do lado do cliente encontrar um erro.

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

event.timeStampnão funciona no Firefox. Retornar false não é padrão, você deve ligar event.preventDefault(). E enquanto estamos nisso, sempre use chaves com uma construção de controle .

Para resumir todas as respostas anteriores, aqui está um plugin que faz o trabalho e funciona em vários navegadores.

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

Para abordar o Kern3l, o método de temporização funciona para mim simplesmente porque estamos tentando parar um clique duplo no botão enviar. Se você tiver um tempo de resposta muito longo para um envio, recomendo substituir o botão ou o formulário de envio por um botão giratório.

O bloqueio completo dos envios subsequentes do formulário, como a maioria dos exemplos acima, tem um efeito colateral ruim: se houver uma falha na rede e eles quiserem tentar reenviá-los, eles não conseguirão fazê-lo e perderão as alterações que fez. Isso definitivamente deixaria um usuário irritado.

Robin Daugherty
fonte
2
Você está certo. Você pode fazer o contrário - desabilitar instantaneamente e depois de um longo tempo, habilitar novamente. Evita cliques duplos, permite reenviar, mas ainda permite reenvios inválidos para usuários com atraso.
Ctrl-C
8

Por favor, confira o plugin jquery-safeform .

Exemplo de uso:

$('.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
fonte
4

... mas o formulário não é enviado com nenhum dado do POST que deveria incluir.

Corrigir. Os nomes / valores dos elementos do formulário desativados não serão enviados ao servidor. Você deve defini-los como elementos somente leitura .

Além disso, as âncoras não podem ser desativadas dessa maneira. Você precisará remover os HREFs (não recomendado) ou impedir o comportamento padrão (melhor maneira), por exemplo:

<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
fonte
@ Adam, sim, qualquer manipulador de cliques anexado será acionado. Eu pensei que você só queria impedi-los de serem modificados?
22610 Karim79 #
@ karim79 Não me importo se os manipuladores de cliques são acionados, só quero garantir que o formulário não seja reenviado pelo usuário clicando em outro botão de envio.
1326 Adam
@ Adam - não, isso não vai acontecer. Eu, meu exemplo, eu adicionei, $('input[type=submit]').attr("disabled", "disabled");mas minha maneira favorita é colocar onclick="this.disabled = 'disabled'"o onclick do envio.
karim79
@ karim79 Eu tentei isso, mas o botão de envio no qual estou clicando não está incluído no POST. Eu preciso que ele seja incluído no POST para determinar qual botão foi clicado.
Adam
Eu sei que você acabou de desvendar o evento click para o 'a', mas por que não usar apenas $ ('a'). Click (function (e) {e.preventDefault (); // ou return false;}); ?
Fabio
4

Existe a possibilidade de melhorar a abordagem de Nathan Long . Você pode substituir a lógica para detecção do formulário já enviado por este:

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
fonte
1
Por que diabos você usaria um cronômetro para essa abordagem?
Bobort
4

Código de Nathan, mas para jQuery Validar plugin

Se você usa o plug-in jQuery Validate, eles já têm o manipulador de envio implementado e, nesse caso, não há razão para implementar mais de um. O código:

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

Você pode expandi-lo facilmente dentro do } else {bloco para desativar as entradas e / ou o botão enviar.

Felicidades

Alph.Dev
fonte
2

Acabei usando as idéias deste post para encontrar uma solução bastante semelhante à versão do 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 assim:

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

Descobri que as soluções que não incluíam algum tempo limite, mas apenas desabilitaram o envio ou desabilitaram os elementos do formulário, causaram problemas porque, uma vez acionado o bloqueio, você não pode enviar novamente até atualizar a página. Isso causa alguns problemas para mim ao fazer coisas com ajax.

Provavelmente isso pode ser um pouco complicado, pois não é tão chique.

jacklin
fonte
2

Se você estiver usando o AJAX para postar um formulário, o conjunto async: falsedeverá impedir envios adicionais antes que o formulário seja limpo:

$("#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
fonte
3
Definir <code> asnyc: true </ code> é uma abordagem ruim, porque ele congela o navegador até que o pedido está completo, que bate a essência do AJAX
Edwin M
2

Modificou a solução de Nathan um pouco para o Bootstrap 3. Isso definirá um texto de carregamento para o botão enviar. Além disso, o tempo limite será excedido após 30 segundos e permitirá que o formulário seja reenviado.

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

Use dois botões de envio.

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

Use um contador simples ao enviar.

    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 chamar a monitor()função em enviar o formulário.

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

Estou tendo problemas semelhantes e minhas soluções são as seguintes.

Se você não possui nenhuma validação do lado do cliente, pode simplesmente usar o método jquery one () conforme documentado aqui.

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

Isso desativa o manipulador depois que ele é invocado.

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

Se você estiver fazendo a validação do lado do cliente como eu, então é um pouco mais complicado. O exemplo acima não permitiria o envio novamente após falha na validação. Tente esta abordagem

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

Você pode parar o segundo envio por este

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

Minha solução:

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

fonte
0

No meu caso, a submissão do formulário tinha algum código de validação, então eu incremento a resposta de Nathan Long, incluindo um ponto de verificação de submissão

$.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
fonte
0

Alterar botão de envio:

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

Com botão normal:

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

Então use a função click:

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

E lembre-se de reativar o botão quando for necessário:

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

Não posso acreditar no bom e velho truque css dos eventos-ponteiro: nenhum ainda não foi mencionado. Eu tive o mesmo problema adicionando um atributo desativado, mas isso não é postado de volta. Experimente o abaixo e substitua #SubmitButton pelo ID do seu botão Enviar.

$(document).on('click', '#SubmitButton', function () {
    $(this).css('pointer-events', 'none');
})
Tom McDonough
fonte
Isso não interrompe o envio de formulários quando o usuário pressiona a Entertecla.
reformada
0

Por que não apenas isso? Isso enviará o formulário, mas também desativará o botão de envio,

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

Além disso, se você estiver usando o jQuery Validate, poderá colocar essas duas linhas em baixo if ($('#myForm').valid()).

gene b.
fonte
0

esse código exibirá o carregamento no rótulo do botão e defina o botão para

desabilite o estado e, após o processamento, reative e retorne o texto original do botão **

$(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
fonte
-1

Resolvi um problema muito semelhante usando:

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