Introduction
I spent the last week playing with the UpdateProgress
control that comes as part of the AJAX Extensions. For the most part, the control itself is quite nice to use but, there was one thing that I found to be a real pain: I could not get the control to hide an area of a page while it was activated. Although this sounds trivial, it got to be a real pain trying to find areas in my page design that would lend well to an UpdateProgress
being displayed and still be intuitive to the user what was happening.
If you have struggled with this already, you know exactly what I'm talking about. If you have not, I'm sure you will. This control will save you a lot of headache.
Using the code
The ExtendedUpdateProgress
control is actually very easy to use. You implement it on a page as you would a regular UpdateProgress
control, with a few added properties. First of all, always make sure you have a script manager on the page.
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
Then, implement the ExtendedUpdateProgress
control on your page (this is a code example from the demo that you can download).
<%@ Register Assembly="UpdateProgress"
Namespace="UpdateProgress.ExtendedUpdateProgress" TagPrefix="cc1" %>
<div id="test1Div">
<asp:Label ID="test1Label" runat="server" Text="Time will appear here">
</asp:Label>
</div>
<cc1:ExtendedUpdateProgress ID="ExtendedUpdateProgress1" runat="server"
AssociatedUpdatePanelID="test1UpdatePanel"
DisplayAfter="400" DivToHide="test1Div"
DynamicLayout="true" ImageToDisplay="Small">
</cc1:ExtendedUpdateProgress>
In order for this control to work, you must specify an AssociatedUpdatePanelID
. The new properties are the DivToHide
property and the ImageToDisplay
property.
In this example, I wanted to dynamically create the template of the UpdateProgress
control with an animated GIF based on the ImageToDisplay
property. You can remove this property and the code that replaces the template from the code in the control if you do not wish to have the same behaviour. If you do this, you will need to identify the ProgressTemplate
as you would with the regular UpdateProgress
control. With it as it is now, the ProgressTemplate
doesn't need to be defined at all.
The DivToHide
property takes the Id
of a div
and, when the UpdateProgress
control is triggered, the control will hide the associated div
. When the UpdateProgress
is done, the div
will re-appear.
Understanding the code
The first thing the ExtendedUpdateProgress
control does is render all the appropriate JavaScript to the page and register itself with its divToHide
, AssociatedUpdatePanel
, and its clientId
. As there can be more than one UpdateProgress
control on a page, we can't hard-code specific IDs and tags in the generated JavaScript. So, we need to register the valid information so we can identify it later. The SetUpdateProgressControl
function is called from a RegisterStartupScript
method off the Page.ClientScript
.
var updateProgressControls
function UpdateProgress(updatePanelId, controlToHideId, updateProgressId)
{
this.ControlToHideId = controlToHideId;
this.UpdatePanelId = updatePanelId;
this.UpdateProgressId = updateProgressId;
}
function SetUpdateProgressControl(updatePanelId,
controlToHideId, updateProgressId)
{
if (updateProgressControls == null)
{
updateProgressControls = new Array();
}
var controlIndex = 0;
for (var i=0, len=updateProgressControls.length; i<len; ++i )
{
if (updateProgressControls[i].UpdatePanelId == updatePanelId){
controlIndex = i;
break;}
controlIndex = controlIndex + 1;
}
updateProgressControls[controlIndex] =
new UpdateProgress(updatePanelId, controlToHideId, updateProgressId);
}
After the appropriate info has been registered to the JavaScript, the PageRequestManager
's BeginRequest
and EndRequest
methods are overridden.
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);
When a postback request is started, it takes a look at the UpdatePanel
that caused the postback. If it has been registered in the ExtendedUpdateProgress
control, then it hides the associated DivToHide
. One special note, as there is not an event that fires when an UpdateProgress
control is activated, I had to take the DisplayAfter
property of the UpdateProgress
and use the window.setTimeout
to delay the function call. I added an extra millisecond to the timeout as the hideControl
checks if the UpdateProgress
is displayed before hiding the associated div
, just an extra precaution.
function BeginRequestHandler(sender, args)
{
var updateControl = GetUpdateControl(sender._postBackSettings.panelID);
if (updateControl != null)
{
var postBackPanelControl = document.getElementById('postBackPanel');
postBackPanelControl.value = sender._postBackSettings.panelID;
var cbk = Function.createCallback(hideControl,
{ControlToHideId: updateControl.ControlToHideId,
UpdateProgressId: updateControl.UpdateProgressId});
window.setTimeout(cbk, " + (DisplayAfter + 1) + ");
}
}
function hideControl(e, context)
{
var controlId = (e.ControlToHideId == null)?
context.ControlToHideId:e.ControlToHideId;
var updateProgressId = (e.UpdateProgressId == null)?
context.UpdateProgressId:e.UpdateProgressId;
var divToHide = document.getElementById(controlId);
var progressDiv = document.getElementById(updateProgressId);
if (progressDiv.style.display != 'none')
{
divToHide.style.visibility = 'hidden';
divToHide.style.display = 'none';
}
}
When the postback request ends, it shows the associated div
.
function EndRequestHandler(sender, args)
{
var postBackPanelControl = document.getElementById('postBackPanel');
var panelId = (sender._postBackSettings.panelID == null)?
postBackPanelControl.value:sender._postBackSettings.panelID;
var updateControl = GetUpdateControl(panelId);
if (updateControl != null)
{
showControl(updateControl.ControlToHideId);
}
postBackPanelControl.value = '';
}
function showControl(controlId)
{
var divToHide = document.getElementById(controlId);
divToHide.style.visibility = 'visible';
divToHide.style.display = 'block';
}
Both the begin and end requests use a function GetUpdateControl
. This function does nothing more than take the panelId
passed in and find the registered UpdateProgress
information for that Panel
.
function GetUpdateControl(panelId)
{
if (panelId == null || panelId == '')
return null;
var rawPanelId = panelId.split('|')[0];
var updatePanelId =
rawPanelId.substring(rawPanelId.lastIndexOf('$')+1, rawPanelId.length)
for (var i=0, len=updateProgressControls.length; i<len; ++i )
{
if (updateProgressControls[i].UpdatePanelId == updatePanelId)
{
return updateProgressControls[i];
}
}
return null;
}
The code behind the ExtendedUpdateProgress
control is fairly extensive, but it has been documented thoroughly and the implementation of the control is extremely easy.
Points of interest
I would definitely recommend taking a look at how this control works. There is extensive JavaScript that gets rendered to the page to allow this control to work and manage itself. It also needs to be able to allow multiple UpdateProgress
controls on one page. Because of this, the JavaScript rendered to the page needs to be able to "register" each control appropriately.
Conclusion
Please note that this code will only work in IE as it is defined right now. I am currently looking into making it compatible for Firefox and Safari, and will post updates as I have them. Updated: April 29. Added support for Firefox and Safari. Tested with Firefox 2.0 and Safari 3.1.1. If you would like a description of the changes, please feel free to ask. Please enjoy the code, and if you have any question/comments/concerns, please do not hesitate to ask.
History
- April 28, 2008 - Initial article.
- April 29, 2008 - Uploaded new source code and demo files to support Firefox and Safari.
- April 30, 2008 - More description has been asked for. Please check out the new "Understanding the code" section above.