How are jQuery event handlers queued and executed?
Asked Answered
O

5

18

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?

Orchestral answered 4/4, 2019 at 6:14 Comment(3)
Side Note: you dont need the .each() part, $('form').submit(...) will do the iteration internallyStrang
@PatrickEvans: thanks a lot for your explanation.Orchestral
I created a fiddle with your code and some console logging, as others have stated, the issue is that you are navigating away from the page. If you throttle the page in chrome, you will see that you are actually hitting your function. jsfiddle.net/vz84j6ak/3Wrongful
S
4

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));
});
Strang answered 6/4, 2019 at 7:26 Comment(0)
T
4

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/

Truck answered 4/4, 2019 at 6:48 Comment(2)
Edited the answer. Please let me know if that helps.Truck
Thanks a lot, there was a small error in the code snippet... I have added jQuery.Validate CDN to your code, to fix the error... now using your sample, I can see that: console.log('A small time difference. So I am stopping') line is executed on double click of submit button.Orchestral
S
4

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));
});
Strang answered 6/4, 2019 at 7:26 Comment(0)
M
1

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>
Modernity answered 4/4, 2019 at 23:28 Comment(2)
Thanks for your answer, This solution works fine, I am just curious why the events are firing in this manner... by the way, I did try disabling submit button see here, but it caused some issues... also I don't want to permanently disable submit button, because there could be some server side errors, and user should be able to resubmit.Orchestral
@HoomanBahreini No problem! I updated my code to show you how the submit button can be toggled based on the success of an arbitrary condition.Modernity
F
1

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.

Femme answered 6/4, 2019 at 7:50 Comment(1)
Thanks a lot @Mihai, you are correct, I had forgotten to include jQuery.validate in the question (but I had it in the real example)... I have updated the question with my real example code.Orchestral
F
0

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.

Fringe answered 6/4, 2019 at 6:41 Comment(2)
Using a click event is usually discouraged as there are other ways to submit a form, hitting enter in a textbbox for instance. With your example i could cause two or more submissions by clicking the button and then hitting enter inside the textbox. The latter will not trigger the click event. Using the submit event will catch all ways of submitting and make sure the check is preformed.Strang
There are lots of ways to submit a form actually, I just wanted to show an idea here. personally I never use a form element, instead I create a div element with a class of form and control everything manually. actually I already created a github repo for that purpose. (AminAdel/ErkinForm.js) ... but not advanced yet.Fringe

© 2022 - 2024 — McMap. All rights reserved.