February 2008
Posted on the 1st at 12:40 AM CST
How to Disable the Submit Button of a Web Form
FiledFiled under ASP.NET, Javascript

I know it has been kinda quiet around here lately, and tonight I finally got some time to post a solution to a legendary web application problem (I also rolled out a few updates to my blog, as you may have already noticed). The issue at-hand probably gets most of its exposure through the use of the ASP.NET framework. On a web form, it is often a requirement to prevent multiple form submissions to maintain efficiency and avoid complications on the server-side. The most visually appealing way to accomplish this is by disabling the submit button as soon as the form submits, essentially stopping the user from going on a clicking spree. Seems like such a trivial task, but unfortunately doing it the normal way will cause problems.

The Problem

When an HTML form gets submitted, an HTTP POST is automatically generated and shipped off to the form tags "action" value. This POST data is obviously comes from the form elements, and this is how data gets from your computer to the server. For example, imagine I have a form with two textboxes and a submit button…

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Test Page</title>
</head>
<body>
  <form id="frmTest" runat="server">
    <asp:TextBox ID="txtBox1" runat="server" Text="Test1"></asp:TextBox>
    <asp:TextBox ID="txtBox2" runat="server" Text="Test2"></asp:TextBox>
    <asp:Button ID="btnSubmit" runat="server" Text="Submit" />
  </form>
</body>
</html>


When that form gets submitted, the following POST is generated (note that I removed the ViewState and EventValidation data, as well as a couple irrelevant HTTP headers from the following POST examples for brevity)…

    POST /Sandbox/default.aspx HTTP/1.1
    Connection: Keep-Alive
    Content-Length: 44
    Content-Type: application/x-www-form-urlencoded
    User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)

    txtBox1=Test1&txtBox2=Test2&btnSubmit=Submit


In case you are not familiar with POST, the bottom line is the actual data. You can see it is a simple collection of name/value pairs. When a control within the form is disabled, it no longer becomes a part of the POST. A disabled control is meant to be read-only, so it saves space by not including them with the POST. And there inlies the problem. Here is the same HTTP post outlined above, except this time I disable the submit button with Javascript once it gets clicked…

    POST /Sandbox/default.aspx HTTP/1.1
    Connection: Keep-Alive
    Content-Length: 44
    Content-Type: application/x-www-form-urlencoded
    User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)

    txtBox1=Test1&txtBox2=Test2


You will notice on the bottom line of the POST that the btnSubmit parameter is no longer being passed. You wouldn't think that this would cause a problem, since all of the data inputted by the user still made it to the server. However, ASP.NET takes this button value much more seriously. Typically, you would have some code-behind for the button click event to process the form…

Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
    'Form processing logic is here
End Sub


ASP.NET will only invoke this click event when the submit button's value is included in the POST. I believe the EventArgument value also has something to do with it, but that's another story for another day. Without that value in the POST, your Page_Load will still get fired, but the code in your button's click event handler will never execute. Granted, you could put some crap code in your Page_Load to look for the existence of certain parameters in the request and react to that accordingly. I think it's safe to say that is nothing close to a reasonable solution. There are a few solutions I have stumbled across, most of which I saw in an ASP.NET forums. None of them were very appealing to me because they required tedious server-side logic that would obviously be a bitch to maintain. I hate wasting time doing maintenace (who doesn't?), so I couldn't settle for that. I wanted a solution that had nothing to do with server-side code; a task this trivial shouldn't! I wanted something that could be implemented on existing forms painlessly. And lastly, I wanted something that could easily be extended and configured to accomodate specific scenarios.

The Basic Idea of My Solution

It's pretty simple, actually. Instead of disabling the submit button itself, I just hide it with Javascript. This is only modifying the style of the control, so the value still comes through with the POST. Then I programatically create a disabled <button> tag with the message of my choice and insert it into the DOM, right before the actual submit button. It will happens so quickly that it will appear that the submit button had been disabled, and it's value changed. Works like a charm!

I have setup a really simple example of this, nothing but pure HTML with all the basic Javascript directly in the markup so you can see the general idea of how it works. That example is not dynamic at all, and it's fairly obtrusive. With that said, I have a much cleaner solution that I'd like to share with you.

Doing Things the Right Way

I finally got some time to do what I wanted with this idea. I created an unobtrusive object model that automatically does everything for you. Once the page loads, it loops through every form and attaches another onsubmit handler to the existing one. If no onsubmit handler exists, or if the existing handler returns true, it will loop through each input element within that form (starting with the last one) and disable each submit button (using the method described above). More specifically, it will disable each <input type="submit"> and <input type="image">. It is designed to stop the loop when it encounters the first textbox for the sake of efficiency, but I have set up a configuration option to toggle that. There is also an option to hide all other buttons on the form when it gets submitted, if they exist (<input type="reset"> and <input type="button">). I put one more option in there to change the document cursor to the "Waiting" icon once the button becomes disabled. I figured that would be one final visual indicator for the user. I put some comments in there explaining the configuration options. The options themselves are directly at the top of the object. Here is the code…

function addLoadEvent(func) {
  if(typeof window.onload != 'function')
    window.onload = func;
  else {
    var oldLoad = window.onload;

    window.onload = function() {
      if(oldLoad) oldLoad();
      func();
    }
  }
}

 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  *                     Button Disabler                         *
  *                       Version 1.0                           *
  *                 Written by Josh Stodola                     *
  *                     January 29, 2008                        *
  *                                                             *
  * * * * * * * * * *CONFIGURATION OPTIONS* * * * * * * * * * * *
  *                                                             *
  *   IsTesting  (Boolean, defaults to false)                   *
  *     When this is set to true, the form will never submit.   *
  *     Use this to confirm that the script is working.         *
  *                                                             *
  *   DisabledButtonValue  (String, defaults to 'Please Wait')  *
  *     This is the value to show on the button once disabled.  *
  *                                                             *
  *   HideNonSubmitButtons  (Boolean, defaults to true)         *
  *     When true, the script will also hide any reset buttons  *
  *     or Javascript buttons it encounters.                    *
  *                                                             *
  *   ShowHourglassCursor  (Boolean, defaults to true)          *
  *     When true, the script will change the cursor to the     *
  *     OS-defined "waiting" symbol to indicate loading.        *
  *                                                             *
  *   StopLoopAtFirstTextbox  (Boolean, defaults to false)      *
  *     When true, the script will stop looping through input   *
  *     elements once it encounters the first textbox. The loop *
  *     begins at the bottom of the form. This can be set to    *
  *     true to make the script more efficient.                 *
  *                                                             *
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

var ButtonDisabler = {
  IsTesting: true,
  DisabledButtonValue: 'Please Wait',
  HideNonSubmitButtons: true,
  ShowHourglassCursor: true,
  StopLoopAtFirstTextbox: false,

  IsCapable: (document.getElementById && document.createElement),
  AddSubmitEvent: function(frm, func) {
    if(typeof frm.onsubmit != 'function')
      frm.onsubmit = func;
    else {
      var oldSub = frm.onsubmit;

      frm.onsubmit = function() {
        if(oldSub) {
          if(oldSub())
            return func();
          else
            return false;
        }
        else
          return func();
      }
    }
  },
  LoadEventHandlers: function() {
    if(ButtonDisabler.IsCapable) {
      for(var i = 0; i < document.forms.length; i++) {
        var frm = document.forms[i];

        ButtonDisabler.AddSubmitEvent(frm, function() {
          ButtonDisabler.DisableForm(frm);
          return !ButtonDisabler.IsTesting;
        });
      }
    }
  },
  DisableForm: function(frm) {
    if(!frm) return;
    var inputs = frm.getElementsByTagName('INPUT');

    for(var j = inputs.length - 1; j >= 0; j--) {
      var elem = inputs[j];

      if(elem.type == 'submit' || elem.type == 'image') {
        var btn = document.createElement('button');

        btn.disabled = true;
        btn.innerHTML = ButtonDisabler.DisabledButtonValue;

        elem.parentNode.insertBefore(btn, elem);
        elem.style.display = 'none';
      }

      if(elem.type == 'reset' || elem.type == 'button') {
        if(ButtonDisabler.HideNonSubmitButtons)
          elem.style.display = 'none';
      }

      if(elem.type == 'text' && ButtonDisabler.StopLoopAtFirstTextbox)
        break;
    }

    if(ButtonDisabler.ShowHourglassCursor)
      document.body.style.cursor = 'wait';

    frm.onsubmit = function() {
      return false;
    }
  }
};

addLoadEvent(ButtonDisabler.LoadEventHandlers);

How to Use the Script

This best part of this script is its ease-of-use. All you have to do is setup the options however you prefer, and import the Javascript into your web page markup (usually in the <head> section) with <script> tags and the script will do the rest. I have tested it in all modern browsers and it works fantastic. I have also tested it with a series of the ASP.NET validator controls and several of my custom form validation methods, all without any problems. There are a few other things you should know about this script…

Web Pages with Multiple Forms
This script is designed to handle pages with multiple forms without any problems. Submit buttons will be disabled on a per-form basis. So, if you submit FormA, FormB will still have a visible, enabled, clickable submit button.
Forms with Multiple Submit Buttons
The script is also designed to handle forms with multiple submit buttons. Once submitted, each and every submit button within the form will become disabled. It does this because the overall purpose is to prevent multiple form submissions. When a form has multiple submit buttons, each of them will submit the same form when clicked, so we must disable them all to achieve the desired effect.

That's it! I have probably read dozens of threads on the ASP.NET forums regarding this problem, and now I am glad that I have solution in place to point people to. Most scripts are not perfect, so if you encounter any kind of oddity or bug that is worth questioning, please feel free to let me know about it through the comments form below.

'Til next time…

Comments (32)
Permalink Comment from Mischa Kroon on February 1st, 2008 at 3:25 AM
I don't get it, when the button is disabled you don't get it in the post.

Which shouldn't be a problem because it can't be clicked because it has been disabled.

So what did I miss ?
Permalink Comment from Josh StodolaEmail on February 1st, 2008 at 7:39 AM
It needs to come in with the POST, that is the problem. The button gets disabled AFTER it has already been clicked. I said this in the post: "ASP.NET will only invoke this click event when the submit button's value is included in the POST".

So, without the value in the POST, the server-side click event will not fire.
Permalink Comment from SeanEmail on February 1st, 2008 at 1:23 PM
PART 1

I use Google's personalized home page to watch for updates on blogs (including this one) and such. When I clicked on the link for this entry, I was directed to "blog.josh420.com/archives/2008/02/how-to-disable-the-submit-button-of-web-form.aspx", which lead me to see "That post was not found!".

I noticed that the actual URL for this page is "blog.josh420.com/archives/2008/02/how-to-disable-the-submit-button-ofweb-form.aspx". The difference lies between the word "of" and "web". Google's link has a dash, the real URL does not.

The weird part is that I used Google's link to view this earlier, or so I thought.

Did you change the page name or URL recently?


PART 2
That's a neat trick that you outline above. I'll keep it in mind for future projects where multiple submits maybe an issue.
Permalink Comment from Josh StodolaEmail on February 1st, 2008 at 1:53 PM
Hi Sean,

Yes, there was a URL rewriting discrepency that caused the post to be inaccessible from my RSS feed, Digg, or DotNetKicks. The problem was with the way it interprets the "a" in the blog title. Because of a silly code error (lack of sleep), it did not strip it out appropriately. I fixed my blog and re-deployed it to the server. Then when I went to update the blog post (I noticed a typo), it corrected the permalink! I never intended to EVER update the permalink field of any blog post after it gets published, so I dont know when I put that code in there or why.

The post was still on the front page of my blog, but all external links were incorrect. I'm embarassed to say that it was like this for about 8 hours. I just changed the subject URL back to what it was before, even though it's not how it really should be. But, I guess permalink means permanent, right?
Permalink Comment from Josh StodolaEmail on February 1st, 2008 at 2:05 PM
I've thought about this situation for a few moments now, and I think I got a plan. I can keep track of every time a blog post title gets updated in a separate MySQL table (1 field for PostID, 1 field for the old PostTitle), and then when a request comes in and the post is not found naturally, I can search that table for the entry that could not be found. If it finds a match, I take the PostID and get the correct permalink and do an automatic 301 redirect. Beautiful!
Permalink Comment from Dave on February 6th, 2008 at 2:09 AM
Hey, Josh.

You can actually do this easier, using the UseSubmitBehavior property of the Button control (in ASP.NET 2.0+). It basically causes the button to __doPostBack instead of relying on the form submission behavior of the browser, leaving you free to disable it on the client side and not interfere with the postback.

http://encosia.com/2007/04/17/disable-a-button-control-during-postback/
Permalink Comment from Josh StodolaEmail on February 6th, 2008 at 9:26 AM
Hi Dave, and thanks for commenting. I have seen that approach on the forums as well. Thanks for posting a link, it's nice to have a collection of methods here so that other visitors can pick and choose what suits their needs best.

The problem I have with any kind of server-side approach to this is that it requires the _doPostback function. I think with the new MVC framework, developers are trying to get rid of this postback/viewstate stuff and are going back to the classic, proven method(s). It also does not seem right for the server-side to have anything to do with a subtle visual enhancement such as this. And maybe this is me being picky, but I don't want inline Javascript at all. I like to keep things as unobtrusive as possible; behavioral separation has numerous benefits. The only inline script you will find on this blog is for the CAPTCHA and the comments with displayed email addresses.

Thanks again for the comment, and have a great day!
Permalink Comment from Ilan on February 11th, 2008 at 1:56 AM
Hi Josh,
I have been looking every where for an "all included solution" like yours.
Sadly i tried it on my site and found that
while using the ASP.NET validation controls with a
validation summary the "disabled button" gets stuck and doesn't let the user fix his input and resubmit.
is there a way that i can check client page validation before invoking your function. Something like the Page.IsValid in C#?
Thanks Ilan
Permalink Comment from Dave on February 14th, 2008 at 7:15 PM
Due to bug 341, I wouldn't dare use the button element in any application in a production environment. Every other browser will not send the innerHTML garbae that IE sends.
See bug report here.
http://webbugtrack.blogspot.com/2007/10/bug-341-button-element-submits-wrong.html
Permalink Comment from Josh StodolaEmail on February 14th, 2008 at 8:59 PM
Thanks for the comment. Although, the <button> tag is inserted into the DOM as a DISABLED control. Any disabled control will not be included in the POST. Therefore, this approach is great.

Regardless, who cares about the garbage that IE sends? It's irrelevant, correct? The server does not give a shit about what this new disabled element will generate. As long as the normal submit button makes its way through, everything will get processed perfectly.

Please tell me if I am wrong. Best regards...
Permalink Comment from Tom Dyer on March 14th, 2008 at 9:33 AM
I love this solution, it is really elegant... but I have a problem....

My form is AJAX enabled and uses an updatepanel. This way, the window.onload function only actually fires once on the first load. At this point the 'Please wait' functionality works perfectly. Problem is every other time I click the button it doesn't work. I had a quick whizz around t'Internet and found a little message board post suggesting using Sys.Application.add_load() instead of window.onload so that the js runs on every postback from the updatepanel but I can't figure out how to modify your script.

Are you able to help at all?
Many thanks in advance

Tom.
Permalink Comment from Josh StodolaEmail on March 14th, 2008 at 11:47 AM
Hi Tom, and thanks for stopping by. What you can try is this...

Remove the bottom line from my script: addLoadEvent(ButtonDisabler.LoadEventHandlers);

In your code-behind, try something like:
Dim Script as String = "Sys.Application.add_load(ButtonDisabler.LoadEventHandlers);"
ClientScript.RegisterStartupScript(Me.GetType(), "load", Script, True)

Please let me know if that works, and I will update the post accordingly. I also have an update to make to the post regarding validation summaries (much thanks to Ilan).
Permalink Comment from Tom Dyer on March 17th, 2008 at 3:52 AM
Hi Josh,

Well I gave it another couple of tries but to no avail.

I popped the above code into the page_load and the updatepanel_load
code behind but it wouldn't play.

After this failed I also tried Sys . UI . DOMEvent . AddHandler to catch the updatepanel load event and pop your script but this didn't work either and kept telling me that the DOM it was trying to catch was NULL or not yet set... I tried this code in a few different places including the 2 methods above and the click event of the button itself but always the same error.

I am all out of ideas (and time for now) though so it is either get rid of the updatepanel or trust the user (LOL!!!!)

Thanks again anyway.
Tom.
Permalink Comment from Tom Dyer on March 18th, 2008 at 6:30 AM
I figured it out Josh, I have emailed you the solution as the scripting was failing spam prevention on here...

Thanks
Tom.
Permalink Comment from Tom Dyer on March 22nd, 2008 at 12:40 PM
I found some time so I have made a technical video blog and I have just posted the update panel solution to this as the first video.

http://www.kruelintent.com/tech

Thanks
Tom.
Permalink Comment from Josh Stodola on March 23rd, 2008 at 2:21 PM
Tom, you are the man! Thanks so much for taking the time to do that. Have a Happy Easter!
Permalink Comment from Sorin Serban on March 27th, 2008 at 3:01 AM
Good to see someone bumped into this also. Gonna try it soon
Permalink Comment from DaveyP on April 30th, 2008 at 6:47 AM
Works perfectly. How would I get it to disable all buttons, but leave the text for all except the one pressed as it is? My Javascript is nonexistant.

Ta
Permalink Comment from Josh StodolaEmail on April 30th, 2008 at 7:38 AM
DaveyP: I will get back to you on this later this evening.
Permalink Comment from Davey on May 1st, 2008 at 11:09 AM
Cheers
Permalink Comment from Mikel on May 14th, 2008 at 3:17 PM
Awesome, you have just made my day :)
Permalink Comment from Mangesh Patil on May 21st, 2008 at 4:10 AM
Hi,
Current script does not work if I have scriptManager tag in my form.
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>

Could you send me the updated script for the same.

Note: The following URL is also not working.
http://www.kruelintent.com/tech
Permalink Comment from deblendewim on May 22nd, 2008 at 8:29 AM
Testing the captcha
Permalink Comment from lavanya on June 4th, 2008 at 11:52 PM
I tried your script itworks great. But I have problem on firefox.

I have assessment test page once student checks answerchoice and hit on submit button, i was to hide the continue button.

Works great on IE. On fire fox when i hit the continue button it wont let me go to next question same question repeats again.
Permalink Comment from lavanya on June 5th, 2008 at 11:19 AM
Can you please let me know who to do for a specific submit button? Thanks.
Permalink Comment from Josh StodolaEmail on June 5th, 2008 at 12:46 PM
To do a specific button, you will have to change the DisableForm function. Just change this line...
    var inputs = frm.getElementsByTagName('INPUT');

To this...
    var inputs = new Array();
    inputs[0] = document.getElementById('YOUR_BUTTON_ID');

You may need to view the source of your rendered page to get the correct button ID. Hope this helps! I'm not sure what your other problem could be in Firefox.
Permalink Comment from lavanya on June 6th, 2008 at 2:13 AM
I tried it. it dosent work same page refreshes again. i can see the please wait button but nothing happens.

here is the another code i used. I am able to take the test but not able www.codeproject.com/…/OneTimeClickableButton.aspx hide the button but style.visibility property. Thanks alot.
Permalink Comment from lavanya on June 6th, 2008 at 7:38 AM
Hi Josh,
I dont have any text boxes in the form. can I eliminate the following code? How do I break the loop? please let me know.

if(elem.type == 'text' && ButtonDisabler.StopLoopAtFirstTextbox)
break;
Permalink Comment from lavanya on June 7th, 2008 at 7:48 PM
I am able to solve my problem. Thanks for the solution.
Permalink Comment from Sameer KulkarniEmail on July 2nd, 2008 at 4:59 AM
If there already exist a onLoad Event on the body tag the code mentioned above does'nt work.
Please suggest an alternative way to do so.
Permalink Comment from Blogging Developer on July 3rd, 2008 at 6:07 AM
Here is my article about how to disable form submit on key press: http://www.bloggingdeveloper.com/post/Disable-Form-Submit-on-Enter-Key-Press.aspx

An alternative approach to the problem :)
Permalink Comment from Josh StodolaEmail on July 11th, 2008 at 8:49 PM
@Sameer: you are correct, that does cause problems. What you need to do is execute your code on the window onload event instead like this...

  window.onload = function() {
    //Your body onload code here
  }

Thanks for pointing this out, and I am sorry for the delayed response.

Guess What?

There are a few basic guidelines you should be aware of before leaving a comment…

  • If you choose to display your email address, it will not be detected by spam bots
  • Comments are limited to 3,000 characters; so far you have used none of them
  • HTML will be encoded; links and line breaks will be converted automatically
  • Comments containing five or more links will be subject to moderation

Have Your Say

← Answer this to prove you are human
 
 

Chill Out…

No Trackbacks