ChatGPT解决这个技术问题 Extra ChatGPT

Prevent double submission of forms in jQuery

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!

Thanks everyone for all your help!

C
Community

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

Just a pointer - wouldn't it be better to cache the $(this) to a var that = $(this)?
@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.
When using jquery validate unobtrusive validation better to check if form is valid. if ($(this).valid)) {$form.data('submitted', true);}
@cck Awesome, I used that. You had an extra closing paren that I removed: if ($(this).valid) {$form.data('submitted', true);
@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!
C
Ctrl-C

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.


+1 because of the firefox tip, but is there any other option besides enabling buttons on page load?
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.
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.
According to the latest jQuery documentation you should be using .prop instead of .attr. Reference: api.jquery.com/prop/#entry-longdesc-1 "The .prop() method should be used to set disabled and checked instead of the .attr() method. "
P
PTK

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

C
Community

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.


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.
M
Max Kamenkov

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

k
karim79

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

@Adam, yes, any attached click handlers will fire. I thought you just wanted to stop them from being modified?
@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 - 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 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
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; }); ?
C
Community

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

Why on earth would you use a timer for this approach?
S
Slava

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


j
jacklin

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.


A
Al Kari

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

Setting asnyc:true is a bad approach because it freezes the browser until the request is complete which beats the essence of AJAX
A
AlexZ

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

i
izmir_LEE

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

M
Mr. Mak

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>

M
Mohammed Zayan

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

m
mattbloke

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

G
Gabe

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.


佚名

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

C
Community

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

D
Dani

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

T
Tom McDonough

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

This doesn't stop form submissions when the user hits the Enter key.
g
gene b.

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()).


D
Dharman

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

u
user2342558

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


N
Nolan

I solved a very similar issue using:

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