Getting the ID of the UpdatePanel which triggered an asynchronous postback

UPDATE 19/11/2006: An updated version of the component compatible with ASP.NET Ajax Extensions Beta 2 has been released, details in this post.

UPDATE 11/11/2006: With the release of ASP.NET Ajax Extensions Beta this post is outdated, and the attached code is no longer working. I'll be posting a new version of the component soon. 

Some time ago I wrote about a web control I created which let you bind two javascript functions to the postback and callback events of Atlas UpdatePanels, thus giving you the choice of executing custom code whenever any of the UpdatePanels of the page posted and called back.
I called it UpdateProgressSignup since its job is similar in some ways to the UpdateProgress built-in control's, in that it performs an action when the asynchronous request begins and another one when the response calls back.
More precisely, the component in charge of raising these events in Atlas is the Sys.WebForms.PageRequestManager, a client-side sealed singleton class which performs the asynchronous requests to the server and returns the results of the response (as well as much more).

How to intercept Atlas postbacks and callbacks

In the first implementation my control was based simply on two members exposed by the PageRequestManager class:

  • propertyChanged event
  • inPostBack boolean property
Thus, using an event handler to hookup the propertyChanged event we got notified whenever the PageRequestManager performed an action:
Sys.WebForms.PageRequestManager.propertyChanged.add(onPropertyChanged);

function onPropertyChanged(sender, args)
{
[...]
}
The args parameter which is passed to our event handler is an object containing, among others, the name of the property which has changed, and the one which concerns us is the inPostBack property of the PageRequestManager class.
If this is the property which has changed it means that either a postback has been initiated or a callback has been received.
To identify in which of the two situations we find ourselves we need to retrieve the actual value of that property, and for this we use the sender parameter, which is actually the singleton instance of the PageRequestManager class. If this property is true, then we are in postback mode, otherwise we are in callback mode. The body of the above function thus becomes:
function onPropertyChanged(sender, args)
{
if(args.get_propertyName() == "inPostBack")
{
if(sender.get_inPostBack())
{
// postback mode
}
else
{
// callback mode
}
}
}
All I did in my control is to make this if/else statement call the javascript functions provided by the user via the control's properties.
Credits for (part of) this solution to the ASP.NET forums.

How to retrieve the ID of the UpdatePanel which triggered the postback
So far so good. Now, a potential problem is: "Ok, I know when any of the UpdatePanels of my page post and call back, but how do I know which one did that?". That's legitimate, but there's no easy answer. The PageRequestManager keeps a list of the UpdatePanels which are on the page, as you can see from the xml-script generated whenever an Atlas ScriptManager control is on the page, which may look like the following fragment:
<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<pageRequestManager id="_PageRequestManager"
updatePanelIDs="UpdatePanel1,UpdatePanel2"
asyncPostbackControlIDs="Button3"
scriptManagerID="ScriptManager1"
form="form1" />
</components>
</page>
</script>
There you can notice one more thing, the asyncPostBackControlIDs attribute. This is the list of controls which work as triggers for the UpdatePanels of the page, and which are thereby placed outside of the UpdatePanels themselves. We'll talk about this later, since it introduces some limits to what we are trying to achieve.
Back to our reasoning, I was gonna tell you that the PageRequestManager object doesn't expose the ID of the UpdatePanel which triggered the postback, so in the start I struggled about how the beneath mechanism got to know which was the UpdatePanel to be actually updated (let alone UpdatePanels whith mode=Always).
Using some tracing tools (FireBug for Firefox and Fiddler) I found out what an Atlas postback looks like when triggered by an UpdatePanel. As even children know already, it is a POST request performed exploiting an object called XMLHttpRequest, whose body look like this:
ScriptManager1=UpdatePanel1&__VIEWSTATE=[...]&__EVENTVALIDATION=[...]&Button1=Get%20Date!
As you can see, and as I had imagined, the ID of the UpdatePanel which triggered the postback is actually sent to the server, along with the actual state of the controls in the panel itself; so I though there should be a way to retrieve the ID of that panel myself, thus being able to pass it to the two custom javascript function in order to give the developer more control and some more information about what was happening.
So I started reading the very very long Atlas.js file, which contains all the required code needed to run Atlas, as well as the code of the PageRequestManager class.

My reasoning was that since the PageRequestManager object doesn't expose the ID of the UpdatePanel - while actually finding it out in order to supply it to the component which builds the request - there should at least be one way to reproduce the same steps that Atlas did to retrieve that coveted ID. And finally I got it!

What the PageRequestManager does in order to retrieve it is - in brief - listening to clicks on the page's form element, and then retrieving the element which was clicked.
With that element, it then navigates backwars all the DOM tree until it finds and UpdatePanel (the PageRequestManager keeps a list of all UpdatePanels of the page, as said before) or doesn't find any. After another bunch of checks, it is then able to know if that UpdatePanel needs to trigger the post back, and in this case, actually perform the task.
As easy as this!

Since the PageRequestManager object doesn't expose such functionality to the public, the only way is to reproduce it, thus listening to the clicks on the form element, and then navigating backwards the parents of that element until we find an UpdatePanel using the array which the PageRequestManager supplies (if one is ever found).
Omitting some minor details, the final step is then to pass the ID of that UpdatePanel to our two javascript functions as a parameter, so that whenever they are invoked we know exactly which UpdatePanel triggered the rountrip. The following functions do the lookup and retrieve that information:
for (var j = _updatePanelIDs.length - 1; j >= 0; j--) 
{
var updatePanel = document.getElementById(uniqueIDToClientID(_updatePanelIDs[j]));

if (updatePanel && updatePanel.contains(element)) {
return createPostbackSettings(true, uniqueIDToClientID(_updatePanelIDs[j]));
}
}

function createPostbackSettings(async, panelID)
{
return { async:async, panelID:panelID };
}
Using this new functionality I updated the code of the original control so that it supplies this information to the custom javascript functions. The dowload links are at the bottom of the post.

What if a trigger initiated the postback?
As you should know, there's at least one more way an UpdatePanel can be updated, and it's by using triggers, which are controls placed outside the UpdatePanel. When these controls fire events or change the values of their properties the UpdatePanel/s bound to them can be updated. Under the point of view of the need of retrieving the ID of the panel which triggered the postback this represents a major issue, since in this case the asynchronous request sent to the server takes this form:
ScriptManager1=ScriptManager1&__VIEWSTATE=[...]&__EVENTVALIDATION=[...]Button3=I%20am%20a%20trigger%20for%20UpdatePanel2
As you can see there's no trace of the UpdatePanel bound to the trigger, and it's bad news since we are not able to retrieve its ID in this case.
The PageRequestManager mechanism, in this case, returns as the panelID property the ID of the ScriptManager object of the page, which furthermore is not an DOM element of the page, unlike the UpdatePanels which are rendered as DIVs, so it's completely useless.

In this situation, that is, when it's a trigger which performs the postback, we are still notified of the event, but we get no ID of any UpdatePanels, on which we could act since they are elements of the DOM, but instead the ID of the ScriptManager, which 99% is ScriptManager1 and most important not an element of the page. Thus, whatever action we want to perform on the parameter supplied to our javascript functions, we must first ensure if we are working on the ID of a page element or on a simple string, whch is the ID of nothing!

Ok, now that I've explained what the underlying framework is doing, I will just teach you how to use the control.

How to use the UpdateProgressSignup control
First, the control comes in two flavours, just like Atlas does. A client -side control and a server-side one. It's a matter of choice, but of course the server-side control is faster to set up. Here's the procedure to set up the server-side control:
  1. Write your javascript functions
    Remember what I told you before, the parameter passed to the functions is a string which can contain either the ID of the UpdatePanel in case the postback has been fired from inside the panel itself - which corresponds to the ID of the DIV element that the panel is rendered as - OR the ID of the ScriptManager, which doesn't correspond to any element of the page. Thus, in order to distinguish the two situations, a simple check using the dollar function is needed.
    The code below shows a sample function I used in the demo supplied with the download, which uses MochiKit to apply a nice effect to the UpdatePanel:
    <script type="text/javascript">   
    function shake(elementID)
    {
    if($(elementID))
    // UpdatePanel ID
    MochiKit.Visual.shake($(elementID));
    else
    // ScriptManager ID
    alert("I am a ScriptManager with ID " + elementID);
    }
    </script>
  2. Drag and drop the UpdateProgressSignup control from the toolbox or register it manually into the webform.
  3. Set its two properties, OnPostback and OnCallback, to the name of the javascript functions you want the control to call on the respective events.
In case you want to use the client-side version of the control, there are the steps:
  1. As before, create your javascript functions.
  2. Make the UpdateProgressSignup.js file available to the page by importing it. It must be placed below the Atlas scripts, so the optimal place to include it is the bottom of the page, between the closing form and body tags:
    <script type="text/javascript" src="Scripts/UpdateProgressSignup.js"></script>
  3. Create an instance of the control by calling the contructor and supplying some parameters. You can put this right below the previous script include. Note that for coherence the instance of the control is given that long name, but any other name goes:
    <script type="text/javascript">
    Sys.WebForms.UpdateProgressSignup =
    new
    Sys.WebForms._UpdateProgressSignup({ postback: shake, callback: pulsate });
    </script>
That's all, you're ready to go.

Just one final note and some Atlas background. Atlas can perform one request at a time, it won't accept two simultaneous asynchronous request. So if one request is being processed, you cannot trigger another UpdatePanel postback even if you click on a button inside it.
This brings to what I wanted to tell you. Downloading the code you may notice that there's an additional boolean property called Exclusive that can be used both in the client and server side implementations. It is true by default and 99% of times you'll leave it as is. It's hard for me to explain what it does since my English is not so perfect, so I'll give you an example, supposing that on postback we apply a shake effect and on callback a pulsating effect to the UpdatePanels. Ok, we have two UpdatePanels on the page which contain each a button, which in turn, on the server, sleep the thread for some seconds; you'll understand why in a sec.
  1. Exclusive = true (default)
    Click on button inside UpdatePanel1 -> UpdatePanel1 shakes.
    While the thread is sleeping on the server click button inside UpdatePanel2.
    When the thread wakes up, it returns the response and UpdatePanel1 pulses.
  2. Exclusive = false
    Click on button inside UpdatePanel1 -> UpdatePanel1 shakes.
    While the thread is sleeping on the server click button inside UpdatePanel2.
    When the thread wakes up, it returns the response and this time is the UpdatePanel2 which pulses.

Notice that it's always the UpdatePanel1 which gets the updated content, even in the second case. The only reason why in the second case it's the UpdatePanel2 which pulses is because the UpdateProgressSignup control registered that when the request triggered by the UpdatePanel1 was executing, we tried triggering the UpdatePanel2 too, and lets us know it by pulsing it instead of the UpdatePanel1.
I don't know if this may become useful, but well, it wasn't difficult to implement so I left it as a choice.

The download links can be found on my website at this page.

kick it on DotNetKicks.com

Published 02 September 2006 11:44 PM by simoneb
Filed under: ,

Comments

# SimoneB's Blog said on 02 September, 2006 06:36 PM
UPDATE 03/09/2006: This post has a follow up, please read this for the updates.One of the most asked...
# DotNetKicks.com said on 02 September, 2006 06:37 PM
You've been kicked (a good thing) - Trackback from DotNetKicks.com
# Sonu said on 04 September, 2006 02:08 PM

Very interesting post Simone.

# Garbin said on 04 September, 2006 02:21 PM

Nice one Simone, thank you! I vote for the screencast, I think it could be useful for users.

# Atlas notes said on 04 September, 2006 02:33 PM

Here are the URLs for two nice Atlas articles about the UpdatePanel: Luis Abreu will show you how to

# Sonu said on 04 September, 2006 02:37 PM

I agree with you Garbin. A screencast will be useful.

# simoneb said on 04 September, 2006 03:35 PM

Thanks guys, I'll record a screencast then.

@Garbin, thank you for the trackback, btw my surname is Busoli ;)

# Garbin said on 05 September, 2006 04:49 AM

oops, fixed :)

# zac said on 02 October, 2006 02:21 AM
Thanks for the Control SimoneB, I tried to add it to an existing project and I keep getting the client side error: "_postbackSettings.panelID has no properties" , the error only occurs when a callback is fired from a custom control which renders as a div and uses the onClick attribute to call the postback. Any ideas why this happens ? ......... I think it is because the _onFormElementClick() function dosen't fire when its called from an element like a div, but i'm not sure Thanks for any help!
# simoneb said on 02 October, 2006 05:43 AM

Hi zac, I didn't investigate the issue you reported but it makes sense to think that the control is having some trouble with a div element triggering a postback. It exploits the same mechanism which Atlas uses to identify the UpdatePanel which needs to be updated, so I don't know why it isn't working. If you can't find a workaround feel free to contact me via the contact link and then send me a sample page where the issue occurs.

# SimoneB's Blog said on 18 November, 2006 08:11 PM

Sometime ago I wrote a post about how to get the ID of the UpdatePanel which triggered an asynchronous

# Ben said on 12 December, 2006 03:11 PM
very nice article
# Zolpidem. said on 22 August, 2008 08:32 PM

Cheap zolpidem persriptions. Zolpidem.

# Lexapro. said on 25 August, 2008 04:05 PM

Lexapro weight. Lexapro. Lexapro weight loss. Lexapro generic.

# Side effects of lexapro. said on 26 August, 2008 07:22 AM

Lexapro and pregnancy. Lexapro. Provigil wellbutrin lexapro.

# Lexapro. said on 02 September, 2008 04:39 PM

Lexapro for panic attacks. Lexapro without prescription. Lexapro. Taking trazadone and lexapro together. Gain weight on lexapro.

This site

Search

Go

This Blog

News

Syndication

Sponsors

  • MaximumASP