Ainara portafotos

Double submit is when a user submits a form on your web page twice (well, technically they can do it more than twice, but you can’t call it a double submit…)
It usually happens when your user double-clicks your Submit button instead of clicking it, but may also happen in two other popular scenarios: your buttons do not provide the user with a visual feedback of being clicked (e.g. when you use an image as your submit button), or when your application takes too long to process the submitted form, and the user impatiently submits it again.

Looking around the web you can find quite a lot of discussions around double submission handling and prevention.

One of the most popular ways of preventing double-submit is to disable the submit button in its onclick event, and then submit the form:

<button type="submit" id="no"  
   onclick="this.disabled=true;document.myform.submit();">Submit</button>

This works for most cases, and because the button is disabled, your user can’t click it a second time.

The back button issue

Let’s pretend that after the user submitted your form, they are redirected to a new page, that displays some information related to the submitted form. The user reviews the information and decides to go back to the form and submit it again. Your user clicks the browser’s back button, and resubmits the form with new data. Good?

Yes, unless your user is on Firefox.

In Firefox, the disabled button will remain disabled after you go back, and your user won’t be able to click it. They’ll have to refresh the page in order to do so, but then they’ll lose everything they’ve already entered in the form, and that’s lame.

The reason this happens, and it is important to understand that, is that Firefox just reloads the page from its cache, and in the cache, the button is in a disabled state.

See this or this for the open bugs on Firefox’s Jira (there are others, even one when the developers say this is the intended behavior)

What can I do on Firefox?

There are a few suggestions out there:

  1. Tell the browser to not cache your page using META cache directives.
    While this works – it is a huge overkill (especially where the other, innocent browsers, are concerned…)

  2. Add autocomplete=”off” to your form element.
    While this works too, it has the added problem of clearing out the values of fields in your form, which is not very nice to your users.

  3. Use Jquery’s ready() function to enable the button.
    This “should work”. But remember – Firefox loads the page from the cache. The ready() event will not be fired (try it with the following code on Firefox and chrome to see the difference. Note that you’ll also need to create success.php or a similar file for the action url)
    <!DOCTYPE html>
    <html>
       <head>
          <title>Duck vs Firefox round 1</title>
          <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
          <script type="text/javascript">
             $(document).ready(function(){
              alert('ready');
             });
          </script>
       </head>
       <body>
        <h1>Round 1!!!</h1>
        <form name="myform" method="post" action="success.php">
             <input type="text" name="textInput"/>
             <button type="submit" id="nono" onclick="this.disabled=true;document.myform.submit();">Submit</button>
        </form>
    </body>
    </html>
  4. Use the body’s tag onload=”" attribute.
    This doesn’t work for the same reason as the previous issue – the load event is never fired when the page is read from Firefox’s cache.

Solution, part I

So, the problem is that Firefox loads the page from its cache, and doesn’t process it the way a normal page is processed, and therefore – the onload/ready events are not raised.
There is, however, one event that does get raised: this is the pageshow event.

The pageshow event is raised by Firefox whenever a page is shown, regardless of whether it is loaded from the cache or not.
You could use it like this:

..
<body onpageshow="document.getElementById('nono').disabled=false">
    <form name="myform" method="post" action="success.php">
        <input type="text" name="textInput"/>
        <button type="submit" id="nono"  onclick="this.disabled=true;document.myform.submit();">Submit</button>
    </form>
</body>
..

You don’t have to add a selector to Firefox, because other browsers (Except Safari which uses it in the same way) will just ignore it, as they never raise the event.

If you want to make sure that the code only runs when it is indeed a page loaded from the cache (and not when the user actually accessed your server), then you could add the following condition:

<body onpageshow="if (event.persisted) {document.getElementById('nono').disabled=false}">
<form name="myform" method="post" action="success.php">
         <input type="text" name="textInput"/>
         <button type="submit" id="nono"  onclick="this.disabled=true;document.myform.submit();">Submit</button>
    </form>
</body>

event.persisted will only be true when the page was loaded from persistent storage (the browser’s cache).

But I am using a page template!

In most frameworks, you use some sort of a page template. This has two implications on using onpageshow:

  1. Adding onpageshow to your body tag will add it to all pages, and most of them will not have a button with the relevant id.
  2. You may not know in advance what are the ids of the disabled elements you wish to re-enable.

There is a possible solution to this problem as well:

function disableMe(el) {
    el.disabled =true;
    el.markDisabled =true
    if (window.addEventListener) {
        //This is an Internet Explorer issue. addEventListener wasn't supported before IE9,
        //and it breaks your page on IE8 and below...
        window.addEventListener('pageshow', pageShowHandler, false);
    }
}

function pageShowHandler(event) {
    if (event.persisted) {
        var arrElements = document.getElementsByTagName('button'); //Assume we only care about re-enabling buttons
        for (var i=0; i<arrElements.length;i++) {
        var btn = arrElements[i];
            if (btn.markDisabled && btn.markDisabled ==1) {
                btn.markDisabled = 0;
                btn.disabled = false;
            }          
        }
        window.removeEventListener('pageshow',pageShowHandler,false);
    }
}

In here, I defined two javascript functions. The first one, disableMe() is used to disable an element like so:

..
<button type="submit" onclick="disableMe(this);document.myform.submit();">
..

The disableMe() function sets disabled=true on the element, and also sets ‘markDisabled’ to true. Note that markDisabled isn’t a real attribute name, and we just add it to the element as a marker. The reason is that you might not want to willy-nilly enable all of the disabled button elements when the user clicks the back-button, as some of them may be disabled because of other reasons (e.g. because the user hasn’t checked a specific checkbox).

The last thing disableMe() does is register the pageShowHandler function as an event listener for the pageshow event. This way, the event is only handeled when it is actually needed. Neat, eh? only pages that require pageshow handling because we manually disabled a button, actually receive it. (If you wanted to, you could test for Firefox here, and not do anything if the user’s browser is different).

Now, pageShowHandler is called by Firefox when the pageshow event is fired. Note that you have to define event as a parameter to the function, otherwise you won’t have it.
If the handler is called when the page is loaded from cache, pageShowHandler will locate all button elements on the page.
This is where the marker attribute comes into play. Note that the function tests for its existence and value – so it only enables buttons that were disabled by the disableMe() function.

The last thing pageShowHandler does is to remove itself from the page. It will get re-attached by disableMe() if needs be.

That’s it!

Popularity: 2% [?]

Tagged with:
 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>