183

I have a form that takes a little while for the server to process. I need to ensure that the user waits and does not attempt to resubmit the form by clicking the button again. I tried using the following jQuery code:

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

When I try this in Firefox everything gets disabled but the form is not submitted with any of the POST data it is supposed to include. I can't use jQuery to submit the form because I need the button to be submitted with the form as there are multiple submit buttons and I determine which was used by which one's value is included in the POST. I need the form to be submitted as it usually is and I need to disable everything right after that happens.

Thanks!

Community
  • 1
  • 1
Adam
  • 43,763
  • 16
  • 104
  • 144
  • Thanks everyone for all your help! – Adam May 13 '10 at 23:03
  • possible duplicate of [Disabling links to stop double-clicks in JQuery](http://stackoverflow.com/questions/1681679/disabling-links-to-stop-double-clicks-in-jquery) – Bo Persson Oct 02 '12 at 18:09

24 Answers24

337

Update in 2018: I just got some points for this old answer, and just wanted to add that the best solution would be to make the operation idempotent so that duplicate submissions are harmless.

Eg, if the form creates an order, put a unique ID in the form. The first time the server sees an order creation request with that id, it should create it and respond "success". Subsequent submissions should also respond "success" (in case the client didn't get the first response) but shouldn't change anything.

Duplicates should be detected via a uniqueness check in the database to prevent race conditions.


I think that your problem is this line:

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

You're disabling ALL the inputs, including, I'd guess, the ones whose data the form is supposed to submit.

To disable just the submit button(s), you could do this:

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

However, I don't think IE will submit the form if even those buttons are disabled. I'd suggest a different approach.

A jQuery plugin to solve it

We just solved this problem with the following code. The trick here is using jQuery's data() to mark the form as already submitted or not. That way, we don't have to mess with the submit buttons, which freaks IE out.

// 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 it like this:

$('form').preventDoubleSubmission();

If there are AJAX forms that should be allowed to submit multiple times per page load, you can give them a class indicating that, then exclude them from your selector like this:

$('form:not(.js-allow-double-submission)').preventDoubleSubmission();
Community
  • 1
  • 1
Nathan Long
  • 122,748
  • 97
  • 336
  • 451
  • 2
    Just a pointer - wouldn't it be better to cache the `$(this)` to a `var that = $(this)`? – Stuart.Sklinar Jun 25 '12 at 08:55
  • 5
    @Stuart.Sklinar - good idea. I used `$form` though - 'form' because it's more descriptive, and the leading `$` because by my convention, that means it's a jQuery object. – Nathan Long Jun 25 '12 at 11:08
  • Just a minor issue - the return $form is outside of scope, and should be inside the bind. – pFrenchie Oct 11 '12 at 02:16
  • @pFrenchie - actually that was a mistake. It should be `return this` to allow chaining calls on the selector, like `$('form.someclass').preventDoubleSubmission().doSomethingElse();` – Nathan Long Oct 11 '12 at 13:35
  • @NathanLong - cool, I've updated my script to match. Thanks for the post - it's a neat workaround. – pFrenchie Oct 12 '12 at 00:31
  • 21
    When using jquery validate unobtrusive validation better to check if form is valid. if ($(this).valid)) {$form.data('submitted', true);} – cck Jul 20 '13 at 09:46
  • 1
    @cck Awesome, I used that. You had an extra closing paren that I removed: if ($(this).valid) {$form.data('submitted', true); – Brian MacKay Oct 10 '13 at 12:49
  • @cck Eh, actually, this seems to be unnecessary. Not sure exactly what is happening - it almost seems as though when you fail unobtrusive validation, jquery.data() gets cleared on the form. Wish I could figure out why. – Brian MacKay Oct 10 '13 at 13:37
  • 1
    @BrianMacKay if form is not valid and submit button is pressed this jquery plugin marks submitted attribute true but unobtrusive validation prevents submitting due to validation errors. After correcting errors one cannot submit the form because submitted attribute is true! – cck Oct 22 '13 at 12:34
  • You can add a timeouted function with sane timeout (~ request roundtrip time + 250ms) to enable the submission again and use it for AJAX forms too... – Tomáš Fejfar Dec 09 '13 at 13:53
  • 1
    @TomášFejfar - you don't have to guess when the request is done; unless the server times out, you should be able to set a callback for request completion. – Nathan Long Dec 11 '13 at 15:41
  • Don't forget that default action for button[type=] (button of which type attribute is empty or set to invalid) behaves as button[type=submit]. See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button – honzajde Mar 19 '14 at 16:02
  • Additionally you could also disable all submit and reset buttons within this form with `$(':reset,:submit', $form).prop('disabled',true)` for a quick user feedback. – Philip Helger Apr 29 '14 at 10:26
  • @PhilipHelger - I mentioned an IE problem with disabling those buttons – Nathan Long Apr 29 '14 at 13:28
  • Yes I saw this, but I think this is a slight difference between "attr" and "prop". I must admit I tested it only with IE9+ and had no problems. – Philip Helger Apr 29 '14 at 14:21
  • I added @NathanLong's solution above to my project code on a page I have a web form and I'm able to triple-click the submit button on my form and get 2 entries into the database instead of one. I've done this now in both FF and Chrome. Any ideas on why this might be? I should add that I did add an alert() msg just prior to the e.preventDefault() line, if that makes a difference. – teaman Aug 22 '14 at 01:22
  • Update: I tried removing the alert() msg from my code. That seems to have reduced the chance of duplicate records being inserted but didn't eliminate it. Or maybe it's possible that the change made no difference and was actually due to my twitchy finger-clicking slowing down in some tests. I will say I could not get IE11 to fail with Nathan's solution so far, but found Chrome36 is the easiest to get to fail. – teaman Aug 22 '14 at 01:42
  • @teaman I'm very surprised that this can fail. My mental model would be: you click twice, two 'submit' events are queued, the browser handles the first one and runs this function, sets the data attribute, and finishes. When it handles the next submit, the attribute is already set. Maybe I'm wrong and the data attribute is set asynchronously, and therefore "set this attribute" gets in line behind the second submit event? Can you experiment/research further? – Nathan Long Aug 22 '14 at 14:43
  • @NathanLong I looked more closely in the document.ready function and it turns out there was another trigger for the form which I am not sure what it was doing. It was the following where awardForm is the form's id value: `$('#awardForm').ajaxForm(function(responseText) { $('#awardForm').parent().html(responseText); });` I took that out and now I'm not able to reproduce the double record entry. I should note it was always just 2 records, never more like 3 or 4, etc. But I had to be quick to get even 2. With the above lines taken out, it's working so far! :) – teaman Aug 22 '14 at 16:48
  • lol at "freaks IE out.". I also found this interesting post on using javascript instead: http://www.the-art-of-web.com/javascript/doublesubmit/ – Wayne Phipps Nov 19 '15 at 10:29
  • Before this call $('form').preventDoubleSubmission(); I had to put $('form').unbind('submit'); As I had a double submission issue with jQuery. – Ben Sep 02 '16 at 09:33
  • 1
    Great, however if you use validation on the form, you may need to click the button a second time after issues are fixed, so use a timeout to run `$form.data('submitted', false); `. – Goose Dec 27 '16 at 20:16
  • Do you think it would be better to disable the submit button and enable it ondocument load to make sure user won't doublesubmit before jQuery is loaded? – Adam Jul 30 '19 at 11:15
52

Timing approach is wrong - how do you know how long the action will take on client's browser?

How to do it

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

When form is submitted it will disable all submit buttons inside.

Remember, in Firefox when you disable a button this state will be remembered when you go back in history. To prevent that you have to enable buttons on page load, for example.

Ctrl-C
  • 4,132
  • 1
  • 26
  • 29
  • 2
    +1 because of the firefox tip, but is there any other option besides enabling buttons on page load? – Raphael Isidro Feb 26 '14 at 18:07
  • 1
    This is the cleanest way to go. I was trying to disable the submit button with a click event handler but my approach was causing issues with chrome. This solution works beautifully. – Anthony To Feb 21 '18 at 06:13
  • 4
    Be very careful with this approach, if you have a value on the submit button and you need it, the form will not submit it. – Fabien Ménager Jan 20 '20 at 14:29
  • According to the latest jQuery documentation you should be using .prop instead of .attr. Reference: https://api.jquery.com/prop/#entry-longdesc-1 "The .prop() method should be used to set disabled and checked instead of the .attr() method. " – J Plana Jan 20 '20 at 20:23
21

I think Nathan Long's answer is the way to go. For me, I am using client-side validation, so I just added a condition that the form be valid.

EDIT: If this is not added, the user will never be able to submit the form if the client-side validation encounters an error.

        // 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
  • 446
  • 4
  • 6
10

event.timeStamp doesn't work in Firefox. Returning false is non-standard, you should call event.preventDefault(). And while we're at it, always use braces with a control construct.

To sum up all of the previous answers, here is a plugin that does the job and works cross-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;
    });
};

To address Kern3l, the timing method works for me simply because we're trying to stop a double-click of the submit button. If you have a very long response time to a submission, I recommend replacing the submit button or form with a spinner.

Completely blocking subsequent submissions of the form, as most of the above examples do, has one bad side-effect: if there is a network failure and they want to try to resubmit, they would be unable to do so and would lose the changes they made. This would definitely make an angry user.

Community
  • 1
  • 1
Robin Daugherty
  • 7,115
  • 4
  • 45
  • 59
  • 2
    You're right. You can go the other way around - instantly disable and then, after some long timeout, enable again. Prevents double clicks, allows resubmit, but still allows invalid resubmits for lagging users. – Ctrl-C Jun 27 '13 at 17:00
8

Please, check out jquery-safeform plugin.

Usage example:

$('.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
  • 2,478
  • 3
  • 22
  • 19
4

...but the form is not submitted with any of the POST data it is supposed to include.

Correct. Disabled form element names/values will not be sent to the server. You should set them as readonly elements.

Also, anchors cannot be disabled like that. You will need to either remove their HREFs (not recommended) or prevent their default behaviour (better way), e.g.:

<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
  • 339,989
  • 67
  • 413
  • 406
  • @Adam, yes, any attached click handlers will fire. I thought you just wanted to stop them from being modified? – karim79 May 13 '10 at 22:14
  • @karim79 I don't care if the click handlers get fired, I just want to make sure that the form doesn't get resubmitted by the user clicking another submit button. – Adam May 13 '10 at 22:15
  • @Adam - no, that won't happen. I my example, I've added `$('input[type=submit]').attr("disabled", "disabled");` but my favourite way is to put `onclick="this.disabled = 'disabled'"` into the submit's onclick. – karim79 May 13 '10 at 22:21
  • @karim79 I tried that, but then the submit button I'm clicking is not included in the POST. I need it to be included in the POST to determine which button was clicked upon – Adam May 13 '10 at 22:26
  • I know that you just unbided the click event for the 'a' but why not just use the $('a').click(function(e) { e.preventDefault(); // or return false; }); ? – Fabio May 13 '10 at 23:06
  • @fabio - because otherwise any other click handlers attached to them will fire first, which might be an unwanted side-effect. – karim79 May 13 '10 at 23:37
  • @Adam - in the button's click event, you could set the value of some other, hidden field before disabling it. Then you'd know which button was clicked. – Nathan Long Jan 05 '11 at 21:49
4

Nathan's code but for jQuery Validate plugin

If you happen to use jQuery Validate plugin, they already have submit handler implemented, and in that case there is no reason to implement more than one. The 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;
    }
  }
});

You can easily expand it within the } else {-block to disable inputs and/or submit button.

Cheers

Slava
  • 2,887
  • 3
  • 30
  • 39
4

There is a possibility to improve Nathan Long's approach. You can replace the logic for detection of already submitted form with this one:

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
Community
  • 1
  • 1
3

You can stop the second submit by this

$("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
  • 859
  • 11
  • 20
2

I ended up using ideas from this post to come up with a solution that is pretty similar to AtZako's version.

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

Using like this:

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

I found that the solutions that didn't include some kind of timeout but just disabled submission or disabled form elements caused problems because once the lock-out is triggered you can't submit again until you refresh the page. That causes some problems for me when doing ajax stuff.

This can probably be prettied up a bit as its not that fancy.

jacklin
  • 2,739
  • 1
  • 24
  • 31
2

If using AJAX to post a form, set async: false should prevent additional submits before the form clears:

$("#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
  • 75
  • 6
  • 3
    Setting asnyc:true is a bad approach because it freezes the browser until the request is complete which beats the essence of AJAX – Edwin M Feb 19 '14 at 13:11
2

Modified Nathan's solution a little for Bootstrap 3. This will set a loading text to the submit button. In addition it will timeout after 30 seconds and allow the form to be resubmitted.

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
  • 244
  • 6
  • 15
2

Use two submit buttons.

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

And jQuery:

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

Use simple counter on submit.

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

And call monitor() function on submit the form.

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

I've been having similar issues and my solution(s) are as follows.

If you don't have any client side validation then you can simply use the jquery one() method as documented here.

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

This disables the handler after its been invoked.

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

If you're doing client side validation as I was doing then its slightly more tricky. The above example would not let you submit again after failed validation. Try this approach instead

$("#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
  • 1,028
  • 1
  • 12
  • 26
1

All of these solutions are passable as long as you're not importing the jQuery validation plugin.

For example, if the client enters invalid input and submits the form, the button will become disabled regardless of whether jQuery validation detects the invalid input. Then the client won't be able to re-submit the form after fixing their input.

The workaround for this issue only took one if statement:

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

After adding this code to my globally-imported JavaScript file, I was unsuccessful in trying to double-post forms on my website.

Gabe
  • 163
  • 1
  • 7
0

this code will display loading on the button label, and set button to

disable state, then after processing, re-enable and return back the original button text**

$(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
});
Dharman
  • 30,962
  • 25
  • 85
  • 135
Mohamed.Abdo
  • 2,054
  • 1
  • 19
  • 12
0

My 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

In my case the form's onsubmit had some validation code, so I increment Nathan Long answer including an onsubmit checkpoint

$.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;
    };
Community
  • 1
  • 1
Bocapio
  • 109
  • 4
0

Change submit button:

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

With normal button:

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

Then use click function:

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

And remember re-enable button when is necesary:

$('#submitButtonId').prop('disabled', false);
Dani
  • 1,825
  • 2
  • 15
  • 29
0

I can't believe the good old fashioned css trick of pointer-events: none hasn't been mentioned yet. I had the same issue by adding a disabled attribute but this doesn't post back. Try the below and replace #SubmitButton with the ID of your submit button.

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

Why not just this -- this will submit the form but also disable the submitting button,

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

Also, if you're using jQuery Validate, you can put these two lines under if ($('#myForm').valid()).

gene b.
  • 10,512
  • 21
  • 115
  • 227
0

The absolute best way I've found is to immediately disable the button when clicked:

$('#myButton').click(function() {
    $('#myButton').prop('disabled', true);
});

And re-enable it when needed, for example:

  • validation failed
  • error while processing the form data by the server, then after an error response using jQuery
user2342558
  • 5,567
  • 5
  • 33
  • 54
-1

I solved a very similar issue using:

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