How are jQuery event handlers queued and executed?


I have an input form, with a submit button. I don't want the user to be able to double click the submit button and double submit the form...

So I have added the following jQuery to my Form:

var prevSubmitTime = new Date('2000-01-01');

function preventFromBeingDoubleSubmitted() {
    $('form').each(function () {
        $(this).submit(function (e) {
            if ($("form").valid()) {
                var curSubmitTime = new Date($.now());

                // prevent the second submit if it is within 2 seconds of the first submit
                if (curSubmitTime - prevSubmitTime < 2000) {
                    e.preventDefault();
                }
                prevSubmitTime = new Date($.now());
            }
        });
    });
}

$(document).ready(function () {
    preventFromBeingDoubleSubmitted();
});

The above code stores the submit time and prevents the second submit, if it is too early (less than 2 seconds), I don't want to permanently disable the submit button, in case there is a server side error...

This code does what I want, but when debugging the code, I can never hit a break point on e.preventDefault();... even if I double click the submit button.

It looks like the second submit event is waiting for the first submit event to complete before firing.

But, if I remove preventFromBeingDoubleSubmitted() function, then I would be able to double submit the form, by double clicking the submit button.

Can anyone explain why sometimes the submit events are fired immediately one after the other... and sometimes it is not the case? Does putting the event handler inside .each(), affects their execution behavior?

Form's when submited by default navigate to the set action url. In the case it isn't explicitly set the url is the current page. The first submit call is going end up starting the navigation process. During this the currently loaded javascript code gets unloaded. This includes event handlers. Hence why you get the inconsistency of being able to double submit or not. If the network request, and other page processes, to the action url happens faster than the speed it takes you to click again the event handlers and your break point won't be called/reached again because they are already unloaded. And vise versa if the network request is slower you would be able to cause the handler to be called and the break point to be reached (if it hasnt already been unloaded).

You say you don't want to permanently disable the submit button, but even if you disable it the form submission is going to cause a page change, and in your example's case this will just load the same page with a new submit button which will not be disabled anymore because its a new page. Thus it is never really permanetly disabeled in the first place.

Now if your real form isn't actually doing a normal form submit, and you are using something like an ajax request, web socket connection, etc then you would set the button to disabled(or set a busy flag) before the request and unset it in the ajax request callback, web socket event,etc.

For example:

jQuery('form').on('submit',function(e){
  e.preventDefault();
  var fd = new FormData(this);
  jQuery('yourbutton').prop('disabled',true);
  fetch('url',{method:"post",body:fd}).then(()=>jQuery('yourbutton').prop('disabled',false));
});

In your snippet I've added a few logs that might be helpful. As you are asking more than one question, I'll answer one by one.

var prevSubmitTime = new Date('2000-01-01');

function preventFromBeingDoubleSubmitted() {
    $('form').each(function () {
        $(this).submit(function (e) {
            console.log('I am going to submit form');
            if ($("form").valid()) {
                var curSubmitTime = new Date($.now());
		console.log('I am a valid form')
                // prevent the second submit if it is within 2 seconds of the first submit
                if (curSubmitTime - prevSubmitTime < 2000) {
                    console.log('A small time difference. So I am stopping')
                    e.preventDefault();
                }
                prevSubmitTime = new Date($.now());
            }
        });
    });
}

$(document).ready(function () {
    preventFromBeingDoubleSubmitted();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.0/jquery.validate.js"></script>

<form id="myform">
    <input type="text" name="q" required="required" />
    <br />
    <input type="text" name="bar" required="required" />
    <br />
    <br />
    <input type="submit" />
</form>

Can anyone explain why sometimes the submit events are fired immediately one after the other... and sometimes it is not the case?

I think you've answered this question yourself. You are adding the code to check if there a difference between time you clicked the submit button the first time versus the second time. If the time difference exists, then you stop the second form submit.

I can never hit a break point on e.preventDefault();

The reason you're not able to get the console is, you're redirecting away from that page when you click the submit button. So the console is cleared. If you want to see the console, use an ajax function to submit the form. And on return, you can probably redirect the page somewhere.

Does putting the event handler inside .each(), affects their execution behavior?

No. It is just an iterator. It will not affect the submit functionality.

I've added a link to the jsfiddle. Adding the alert before preventDefault will stop page from redirecting momentarily. This will prove that the execution happened.

http://jsfiddle.net/2vugwyfe/


You solution is way too overcomplicated. The easiest way to prevent a double submit would be to disable the submit button on submission.

Example:

var submittable = false;
$('form').submit(function (e) {
  if (!submittable) {
    e.preventDefault();
    var $this = $(this);
    var $submitButton = $this.find('button[type="submit"]');
    $submitButton.attr('disabled', true);

    if (CONDITION_SATISFIED) {
      submittable = true;
      $this.submit()
    } else {
      $submitButton.attr('disabled', false);
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<button type="submit">Submit</button>
</form>


If you add e.preventDefault(); just before doing $("form").valid(), you will see there's an error thrown.

script.js:7 Uncaught TypeError: $(...).valid is not a function
at HTMLFormElement.<anonymous> (script.js:7)
at HTMLFormElement.dispatch (jquery.min.js:2)
at HTMLFormElement.y.handle (jquery.min.js:2)

This error wasn't visible at first because the submit actually changes the page (refreshes the page in this case) if nothing else is implemented.

However, in general the practice is navigating to another page after a form submission. If you still want to go with your approach and limit the number of submitting, I suggest keeping the submitted state in a local variable and change it according to the validation on the server side.

Last thing.. I don't understand the iteration through the forms since you have only one in your HTML -> $('form').each is useless.


I know what you want, but you made it very complicated. instead of inserting a submit button just add a simple div and add a click handler on that.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="myform">
    <input type="text" name="myInput" />
    <div id="submit" onclick="myform_submit()" />
</form>

and :

function myform_submit() {
    if ($('#submit').hasClass('busy')) { return; }
    $('#submit').addClass('busy');

    // do the tasks then remove the `busy` class :
    $('#submit').removeClass('busy');
}

I just show the idea, you can do better.