Search code examples
javascriptjqueryhtmlformsusability

How to prevent double form submit without losing any data?


I have a HTML form with two buttons

<form action="...">
    ...
    <input type="submit" name="button1" value="Localized button 1">
    <input type="submit" name="button2" value="Otro botón">
<form>

and following JavaScript code

// prevent double submit and display a nice animation while submitting
$(document).on("submit", "form", function(event)
{
    $(this).find("input[type=submit], button").each(function()
    {
        var $button = $(this);
        $button.attr("disabled", "disabled");
        $button.addClass("submitting");
        setTimeout(function()
        {
            $button.removeAttr("disabled");
            $button.removeClass("submitting");
        }, 10000); // remove disabled status after timeout (ms)
    });
});

(plus some CSS transitions and animations to make .submitting look nice)

The above code tries to prevent double form submission because some people seem to misunderstand the web and keep double clicking normal form submit buttons. Some other people have problems with touchscreens due shaking hands or poorly working broken mouse buttons so this about usability, too.

The problem is that Google Chrome runs the event handler on("submit") before collecting the data to be sent with the form submission. As a result, the HTML5 rule of not sending the input value if its control is disabled gets in the way and prevents any submit buttons from being included in the request that the server can see. (The server is looking for parameter button1 or button2 but ignores actual value because it's a localized string due historical HTML form rules.)

How to disable submit buttons on browsers that honor HTML5 rules and still get info about the actual button that was pressed? I very much want to have a solution that has semantically correct HTML5 source. Is there a way to run code after the form data has been collected and about to be transferred to the server?

Somewhat related non-duplicates (please, do not flag this as duplicate of any of these):

Additional requirements:

  • Allow re-submitting the form after a delay (10 sec in the example above) to allow user to workaround e.g. interrupted network connection or to do intentional double submission.
  • No racy solutions (e.g. running the above code with a short timeout after the actual on("submit") event hoping that the browser has collected the data but the user isn't fast enough to double click the button.
  • The form should still work with JavaScript disabled - not having double form submit prevention in this case is okay.
  • No hacks such as adding a hidden form input containing the missing parameter into the DOM.

Solution

  • Here's an implementation that seems to be robust enough for production use:

        // prevent double submit and display a nice animation while submitting
        $(document).on("submit", "form", function(event)
        {
            $(this).find("input[type=submit], button").each(function()
            {
                var $button = $(this);
    
                if ($button.attr("disabled"))
                    return; // this button is already disabled, do not touch it
    
                setTimeout(function()
                {
                    $button.attr("disabled", "disabled");
                    $button.addClass("submitting");
                    setTimeout(function()
                    {
                        $button.removeAttr("disabled");
                        $button.removeClass("submitting");
                    }, 10000); // remove disabled status after timeout (ms)
                }, 0); // let the event loop run before disabling the buttons to allow form submission to collect form data (and emit "formdata" event) before disabling any buttons and hope that user is not fast enough to double submit before this happens
            });
        });
    

    The setTimeout(..., 0) is a hack to allow Chrome to run its event loop once to get Chrome to collect the form data before we disable the buttons which would drop the info about the button from the submitted data. As far as I know, this is not racy with user input because we can use delay of 0 here and there's no way for user to enter any input with zero delay.