Why my ASP.NET AJAX forms are never submitted twice
AJAX, ASP.NET, JavaScript, UI By Dave Ward on March 4th, 2008
The overzealous double-clickers amongst our users often make it desirable to temporarily disable the controls that trigger server side processing. Previously, I’ve shown you how to disable a button during a postback, how to disable a button during a partial postback, and even written a server control to automate the latter.
However, what if you wanted to be more thorough and disable all of the buttons on a page?
In this post, I’m going to show you how to do just that. I’ll also show you how to disable only the buttons in the UpdatePanel raising the event. Finally, for the jQuery users out there, I’ll show you how to simplify the process down to one line of code.
Building a demonstration page
To demonstrate this technique, we’re going to need a page with several buttons that generate partial postbacks. An expanded version of the typical Hello World AJAX sample (a la DateTime.Now) will get the job done:
<asp:ScriptManager runat="server" ID="ScriptManager1" /> <asp:UpdatePanel runat="server" ID="UpdatePanel1"> <ContentTemplate> <asp:Button runat="server" ID="Button1" Text="Button1" onclick="Button1_Click" /> <asp:Literal runat="server" ID="Literal1" /> <asp:Button runat="server" ID="Button2" Text="Button2" onclick="Button2_Click" /> <asp:Literal runat="server" ID="Literal2" /> </ContentTemplate> </asp:UpdatePanel>
protected void Button1_Click(object sender, EventArgs e) { System.Threading.Thread.Sleep(1500); Literal1.Text = DateTime.Now.ToString(); } protected void Button2_Click(object sender, EventArgs e) { System.Threading.Thread.Sleep(1500); Literal2.Text = DateTime.Now.ToString(); }
Using getElementsByTagName to find the buttons
You’re probably familiar with the JavaScript function getElementById and our handy $get shortcut for it, but how about GetElementsByTagName?
This useful JavaScript function returns an array of elements matching a specified HTML tag name. For example, document.getElementsByTagName(’div’) would return an array of every <div> on the page.
Since we know that ASP.NET Button controls render as <input type=”submit”> elements, the solution is clear. We can use a BeginRequest and EndRequest handler to put that to work during a partial postback:
function BeginRequest(sender, args) { // Get an array of all the input elements on the page. var inputs = document.getElementsByTagName('input'); // Loop through the elements and check each one's type. // if it's a submit type element, disable it. for (element in inputs) if (inputs[element].type == 'submit') inputs[element].disabled = true; } function EndRequest(sender, args) { // Get an array of all the input elements on the page. var inputs = document.getElementsByTagName('input'); // Loop through the elements and check each one's type. // if it's a submit type element, enable it. for (element in inputs) if (inputs[element].type == 'submit') inputs[element].disabled = false; }
Limiting the technique to a single UpdatePanel’s Buttons
A nice feature of getElementsByTagName is that it can be used to search the children of most any element. So, if you’d like to limit this functionality to a particular region of a page, that’s easy too.
An UpdatePanel renders its ContentTemplate as a <div> element. With that in mind, the trick here is to call getElementsByTagName on the <div>’s children instead of on the entire document. To do so, only a few changes to the code are needed:
function BeginRequest(sender, args) { // Find the ID of the UpdatePanel raising the partial postback. var panelID = sender._postBackSettings.panelID.split("|")[0]; // Using that ID, get a reference to that UpdatePanel's div. var sendingPanel = document.getElementById(sendingPanelID); // Get an array of all the input elements in that div. var inputs = sendingPanel.getElementsByTagName('input'); // Loop through the elements and check each one's type. // if it's a submit type element, disable it. for (element in inputs) if (inputs[element].type == 'submit') inputs[element].disabled = true; } function EndRequest(sender, args) { // Find the ID of the UpdatePanel raising the partial postback. var panelID = sender._postBackSettings.panelID.split("|")[0]; // Using that ID, get a reference to that UpdatePanel's div. var sendingPanel = document.getElementById(sendingPanelID); // Get an array of all the input elements in that div. var inputs = sendingPanel.getElementsByTagName('input'); // Loop through the elements and check each one's type. // if it's a submit type element, disable it. for (element in inputs) if (inputs[element].type == 'submit') inputs[element].disabled = false; }
As you can see, it’s basically the same code. It takes a slight bit more work to find which UpdatePanel raised the partial postback, but it’s identical after that.
You can use this same method to target the children of any control that renders as an HTML element. This includes Accordion panels, Wizard steps, TabPanel containers, and many others. If you’re unsure how a particular control renders, using FireBug to inspect your page can dramatically accelerate the discovery process.
jQuery could become your new best friend
If you haven’t tried using jQuery with ASP.NET AJAX before, I highly recommend it. To give you an idea of why, here’s how you could use jQuery to re-write the first example:
function BeginRequest(sender, args) { $('input[type=submit]').attr('disabled', true); } function EndRequest(sender, args) { $('input[type=submit]').attr('disabled', false) }
That’s all there is to it. Really.
The UpdatePanel targeted functionality can also be accomplished with similarly concise jQuery code:
function BeginRequest(sender, args) { var panelID = sender._postBackSettings.panelID.split("|")[0]; $('#' + panelID + ' input[type=submit]').attr('disabled', true); } function EndRequest(sender, args) { var panelID = sender._postBackSettings.panelID.split("|")[0]; $('#' + panelID + ' input[type=submit]').attr('disabled', false) }
It’s hard not to love that!
Conclusion: We’ve only just begun
In your own implementation, you might want to limit the technique to only a certain, focal UpdatePanel (or other container) instead of any that raises a postback. You might also want to add a few UI improvements, such as changing button text, blurring the buttons, or changing the mouse cursor during the partial postback.
All of this is possible, with only slight modifications to the sample code. In fact, the download below includes the blurring (it makes a noticeable difference in FireFox).
Additionally, keep in mind that this code contains no error handling. I’m assuming that there are buttons on the page, buttons in every UpdatePanel, and that a button inside an UpdatePanel raises every event.
In production, you should definitely be more careful than I have been here. At very least, check that the array is actually an array before trying to iterate over it.
Source download
Finally, here is the source code download for the full demonstration page, including the client script for the second example:
Shameless plug
If you like the idea of having this functionality, but don’t like the idea of having to implement it, then keep an eye out for the next version of PostBack Ritalin. I’ll be integrating this into it as an optional feature in the next release.
Possibly related posts
What do you think? Your comments are welcomed.
I appreciate all of your comments, questions, and other feedback, but please try to stay on topic. If you have a question unrelated to this post, I recommend posting on the ASP.NET forums or Stack Overflow instead.
If you're replying to an existing comment, please use the threading feature. To do this, click the "Reply to this comment" link underneath the comment you're replying to.

Your comments
Interesting idea, I’ve had to deal with that quite a bit myself these days, although I handled it a bit differently.
I use the UpdateProgress control to display a div over the entire screen while an ajax process is running. The div is transparent (partially) but covers all of the controls so no buttons, or anything else, can be used.
I stole the idea from the Asp.Net AJAX Toolkit ModalPopup sample.
Also, I would agree that all web developers should learn JQuery. Here post I put up about checking all of the checkboxes in a Asp.Net DataGrid. http://elegantcode.com/2007/12/27/check-all-with-jquery-and-a-aspnet-gridview/
That can be another good way to do it. Just make sure that there’s some sort of progress indication, in conjunction with the overlay div. Otherwise, you may end up with some users reloading the page.
Since you’re using jQuery already, you might be interested in a plugin called BlockUI. It’s one of the easiest ways I’ve found to create quick modal popups.
Yes, I usually have a simple animated gif in the center of the screen. It isn’t a true progress bar, but it gives the illusion that something is happening. It is far easier to implement than a true progress bar, and in the end, the users really don’t care anyway (to a point, of course).
That is the fun of UI design and speed. Speed is more about appearance than actual measurement. I user will happily wait for 10 seconds if they have the illusion that something is happening. Then when the wait goes down to 5 seconds they rarely notice.
And I’ll take a look at BlockUI. I’m always looking for a better mouse trap.
Yeah, but what really burns me is when there is an indicator displayed on the screen, but the AJAX request causes a Javascript error (attention Twitter!). So you are sitting there, assuming something is happening, when it really is not.
you should add code to remember which buttons were already disabled before the request and not enable them after the request.
Agreed.
I’ve been looking over all your examples of using buttons with ajax. I wonder why all of them leave out how to use them in combination with the validation controls.
I generally try to avoid complicating my examples with too much extraneous code, so that the central topic is as clear as possible.
In your jQuery example, panelID returns an ID that looks like “ctl00$cphMain$upWhatever” in my site using Master Pages, even though the outputted ID on the page is “ctl100_cphMain_upWhatever”. So, I have to add .replace(/\$/g, ‘_’) to the end of the panelID assignment in order to replace all of the $s with _s and have jQuery be able to find it. I feel like I’ve run across this issue before…
But this method will not work, the update panel trigger is outside the update panel.
I downloaded the sample for this and it ran great.
The only difference between the sample and my project is that I use a master page.
In the script manager (which must appear on the master page), I added the
section to the script manager, but the code still doesn’t work.
What else am I missing?
I would have expected it to work that way.
Try using a ScriptManagerProxy on the content page, and include the ScriptReference there instead of on the master page.
I just tried it and it didn’t seem to work. I was sure that would solve it.
Oh well… maybe I’ll just redesign my page to only have one button. I might be able to get away with that.
Thanks for your help though.
This is a GREAT site and I absolutely LUV PostBackRitalin.
Maybe you can include this set of functionality in there soon.
Thanks again.
I had the same problem as tmc until I put the buttons inside an UpdatePanel, then it’s works just fine.
Hi Dave,
I have been developing a custom button (inherited from a link button) that should prevent a duplicate post back for both full or partial post backs.
Would you use the same approach for a control that is not a true button?
Link buttons are a completely different animal. There’s no standard disabled property for <a> elements.
What you can do is add some logic that causes the link to add an onclick=”return false;” event handler to itself.
Also, while you’re building your own control, make sure to do something on the server side that will double check for duplicate entries by users with JavaScript disabled (or who have purposely circumvented the client-side mechanism).
wow you the best! thanx a lot.
I realize this is an old post, but I found it in a search and while it was really super helpful, I thought I’d offer a tip.
In jQuery 1.2 (which was current at time of this publishing) the old XPath selector syntax such as your input[@type=submit] was functional but deprecated. In jQuery 1.3, it flat-out won’t work with that at-symbol in there. If you drop the at, it works as expected. Might be worth updating the post to be clear on that subtle distinction. :)
Thanks for pointing that out, Brian. I updated the post.