Disabling a User Interface Element During a Partial Page Postback
By Scott Mitchell
Introduction
When using Microsoft's ASP.NET AJAX framework and an UpdatePanel whose ChildrenAsTriggers is set to True (the default), anytime a user interface element
within the UpdatePanel would normally cause a full page postback, a partial page postback is performed instead. For example, clicking a Button Web control
or selecting a different item from a DropDownList control whose AutoPostBack property is set to True normally causes a full page postback, but
if these controls are within an UpdatePanel, a partial page postback occurs instead. But what happens if a partial page postback is taking a while to complete
and the user triggers the partial page postback again? Or what if during this lull she clicks some other Button in the same UpdatePanel, thereby causing a
second partial page postback to ensue?
If a partial page postback is made from the same UpdatePanel while a partial page postback is ongoing, the first partial page postback is aborted and the
second postback commences. Aborting a partial page postback simply means that the ASP.NET AJAX framework on the browser no long listens for a response back
from the server for that request. It does not stop processing on the server, or rollback any state changes that may have occurred on the server. Consequently,
if on a partial page postback you are inserting records into a database or making some other state change, if a user clicks a Button in an UpdatePanel to
instigate a partial page postback, but then clicks the same Button again while the partial page postback is still ongoing, there will be two duplicate
records inserted in the database.
There are a couple of ways to prevent the user from resubmitting a partial page postback while it's still ongoing. The most effective way, in my opinion,
is to "freeze" the frame by overlaying the screen with a <div> element. (See the final demo in the article,
Providing Visual Feedback with the UpdateProgress Control.) Another option is
to disable the user interface element that triggered the postback during the partial page postback lifecycle. This prevents the user from resubmitting
the partial page postback. Read on to learn more!
The Need for Disabling the User Interface During a Partial Page Postback
The download available at the end of this article includes a demo named NoDisabling.aspx that illustrates the default behavior. The demo
consists of an UpdatePanel control containing two Button Web controls. On the first page load, a Session variable named NumberOfClicks is set to 0:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
Session("NumberOfClicks") = 0
End If
End Sub
In the Buttons' Click event handlers this Session variable is incremented and the Button that was clicked along with the value of the
Session variable are displayed in the Results Label control. What's more, both Click event handlers sleep for five seconds to add an artifical
delay in the partial page postback. This delay is present to mimic the time it might take to perform the partial page postback in a real-world setting.
The Click event handler for Button1 follows:
Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Session("NumberOfClicks") += 1
Results.Text = String.Format("Button 1 was clicked... The Buttons have been clicked {0:d} times", Session("NumberOfClicks"))
Thread.Sleep(5000)
End Sub
A recent article of mine, Performing Client Actions in Response to Partial Page Postbacks,
showed how to execute client script at various stages during the partial page postback's lifecycle. If you haven't yet read this article, please do so before
continuing here. In the article I showed how to build a client-side event logger that displays a message as each client-side event of the
PageRequestManager object is raised. The demo available for download includes such functionality.
To see the need for disabling the user interface during a partial page postback, visit the demo page through a browser. Click the first button to instigate
a partial page postback. The event log dislays two entries:
initializeRequest
beginRequest
The next event, endRequest, is not displayed until the response is returned from the server (or if the partial page postback is aborted).
During the partial page postback, click the first button again. This starts the second partial page postback. In the first stage of the second partial
page postback (initializeRequest), the first partial page postback is aborted. This behavior is evidenced by the event log in the screen shot
below. Note that the first two events, initializeRequest and beginRequest, occurred at 11:08:14, and were triggered by clicking
"Button 1" for the first time. Two seconds later, I clicked "Button 1" again. This triggered the third event log entry, initializeRequest, at
11:08:16. Because the first postback is still outstanding, it is automatically aborted. The fourth event log entry, endRequest, indicates the
conclusion of the first partial page postback. And the fifth event log entry (beginRequest) signals the second stage of the second partial
page postback.
What's important to understand is that aborting a postback causes the client API to stop listening for a response, but it does not stop processing on the server
or rollback any state changes. This is made clear by the output displayed in the Results Label at the conclusion of the second partial page
postback. Even though the first partial page postback was aborted, the Session variable has been incremented twice.
In cases where the partial page postback performs some sort of state modification it is prudent to prevent the user from re-triggering the partial page
postback when the partial page postback is ongoing. This article examines one approach for accomplishing this functionality: by disabling the user interface
element that caused the partial page postback.
Determining the User Interface Element that Triggered the Partial Page Postback and Disabling It
As we've already seen, whenever a partial page postback commences, the PageRequestManager raises a series of client-side events. The first one is
initializeRequest. Following that, the
beginRequest event is raised. The beginRequest is
passed an args object that includes information on what user interface element caused the partial page postback. Specifically, this information
can be retrieved via args.get_postBackElement(). Once we have a reference to the user interface element that caused the postback, we can
disable it by setting its disabled property to true:
function PageRequestManager_beginRequest(sender, args)
{
var postbackElem = args.get_postBackElement();
postbackElem.disabled = true;
}
This code disables the user interface element at the start of the partial page postback (before the actual HTTP request is transmitted from the client
to the server). However, this code alone is not sufficient - we need to enable the user interface element once the partial page postback completes.
Re-Enabling the User Interface Element that Triggered the Partial Page Postback
The PageRequestManager raises its endRequest event at the
conclusion of a partial page postback, regardless of whether the partial page postback completed successfully, ended from an error, or was canceled or
aborted from client-side script or by user action. Therefore, it makes sense to re-enable the user interface element in the endRequest event
handler.
Unfortunately, the endRequest event handler is not passed a reference to the control that triggered the partial page postback.
Therefore, we need to save this information in a page-level variable. The following JavaScript illustrates how to accomplish this. Note the page-level
variable, uiId. This variable is assigned the id attribute of the element that caused the partial page postback in the
beginRequest event handler. It is then used in the endRequest event handler to get a reference to the element and set its
disabled property to false.
var uiId = '';
function PageRequestManager_beginRequest(sender, args)
{
var postbackElem = args.get_postBackElement(); uiId = postbackElem.id;
postbackElem.disabled = true;
}
function PageRequestManager_endRequest(sender, args)
{
$get(uiId).disabled = false;
}
Note that in the endRequest event handler I use the $get(id)
function to get a programmatic reference to the element specified by the ID value uiId. $get(id) is a function that
is part of the ASP.NET AJAX framework's client API. It serves as a shortcut to the Sys.UI.DomElement class's getElementById
function.
Testing the Disabled UI Functionality
The download available at the end of this article includes a demo named Disabling.aspx that has the same user interface as NoDisabling.aspx,
but implements the client-side disabling/enabling logic we just discussed. The following screen shot shows the page shortly after "Button 1" has been clicked.
Note that "Button 1" is disabled - it does not respond to clicks until it is re-enabled.
After the partial page postback completes, "Button 1" is re-enabled and can be clicked again to perform another partial page postback.
Stopping Other Partial Page Postbacks
Disabling the user interface element that triggers the partial page postback ensures that the same control won't initiate another postback, thereby
aborting its ongoing request. But what if another element in the same UpdatePanel triggers a partial page postback? For example, what happens if, in
the Disabling.aspx demo, the user clicks "Button 1" and then, during the postback, clicks "Button 2"? Clicking "Button 2" aborts the "Button 1"
postback and then initiates the "Button 2" partial page postback logic.
To prevent this we can either disable the entire user interface in the event of a partial page postback (instead of just "Button 1"). See the final demo in
the Providing Visual Feedback with the UpdateProgress Control article for such an
example. Another option is to programmatically prevent a partial page postback from starting if there is an ongoing one.
The sender object passed into the initializeRequest event handler has a function named get_isInAsyncPostBack()
that returns a Boolean value indiciating if there is an ongoing partial page postback. To permit only one partial page postback at a time, check this
function and cancel the current request if it returns true. The following JavaScript snippet illustrates how to accomplish this:
function PageRequestManager_initializeRequest(sender, args)
{
if (sender.get_isInAsyncPostBack())
{
args.set_cancel(true);
alert('The page is currently serving a request. Please wait until this request completes, then try again.');
}
}
In addition to canceling the requested partial page postback, the above code also displays an alert explaining to the user that their current request
cannot be processed until the ongoing request finishes. The following screen shot shows this functionality in action. "Button 1" has been clicked and
is in the midst of a partial page postback. Because of the script we added in the previous section, the button is disabled. If the user clicks "Button 2"
during the partial page postback, an alert box is shown and the partial page postback request from "Button 2" is canceled.
Conclusion
In some circumstances, a partial page postback performs some action on the server that affects the state of the web application. For such partial page postbacks
it's usually important that the user does not repeatedly cause the same partial page postback. For example, if the partial page postback inserts a record
into the database, triggering the same partial page postback multiple times - by, say, clicking a Button repeatedly - may result in duplicate database
entries. To remedy this you can either disable the entire user interface by means of an overlaid <div> element or you can more
selectively disable the user interface element that triggered the partial page postback for the duration of the postback. This article examined the
latter approach.