UpdateProgress and disable a control on postback with multiple UpdatePanels

 

A few months ago, while upgrading an application to use ajax, I needed to show some feedback to the user on long running actions while disabling some of the controls on the page. Looking around for a solutions, I found a couple of good articles on Matt Berseth's blog about UpdateProgress animations that look promising so I decided to create an extender control to do just that. After I created the extender I found out that Matt had a similar extender as well. Anyway the basic requirements were:

- It must disable a group of controls specified at design time
- It must be able to provide feedback to the user (i.e. show a "Loading..." indicator)
- It must work on FireFox IE6 and IE7
- It must work for nested UpdatePanels
- It must work for strange css layouts (ie. positioning absolute/relative)

The basic principle is to create a div that covers the group of controls that we want to disable, the same way the modal popup works (this could be a div, gridview, list, updatepanel, etc.), show it on the client side's beginRequest event and then hide it on the endRequest event.

The first thing we need to do is to create the div element that will covert our target control and set some initial properties for it. We do this in the initialize function of our extender.   

        this._backgroundElement = $get("loadingExtenderBackgroundElement");
        if( this._backgroundElement == null ) {
            // create a new div to place on top of the control we are updating...
            this._backgroundElement = document.createElement('div');
            this._backgroundElement.id = 'loadingExtenderBackgroundElement';
            this._backgroundElement.style.display = 'none';
            this._backgroundElement.style.left = '0px';
            this._backgroundElement.style.top = '0px';

            // just define a zIndex big enough to sit on top of all the other elements
            // on the page
            this._backgroundElement.style.zIndex = 10000;
            if (this._updatingCssClass) {
                this._backgroundElement.className = this._updatingCssClass;
            }

Note that we first check if we have created a div already, so we don't end up with lots of divs in our code. Next we need to set the position style for the div; usually we'd want to use 'fixed' so no matter what the scroll position is, we get the div position correctly, but IE6 doesn't support the fixed position so we change it for absolute

            // now since IE6 does not support the fixed position style, then we need to hack it a bit
            // to get it to show correctly on top of the element
            if( Sys.Browser.agent == Sys.Browser.InternetExplorer && Sys.Browser.version < 7 ) {
                this._backgroundElement.style.position = 'absolute';
            }
            else {
                this._backgroundElement.style.position = 'fixed';
            }

and we add our div to the form element so we don't have to worry about offsetParent stuff.

document.forms.item(0).appendChild(this._backgroundElement);

Now that we have our background div, we will change the zIndex and relative position in the DOM of the "loading..." div.

        if( this._loadingDiv ) {
            var upDiv = $get(this._loadingDiv);
            if( upDiv.parentNode != document.forms.item(0) ) {
                if( upDiv.parentNode.removeChild(upDiv) ) {
                    document.forms.item(0).appendChild(upDiv);
                }
            }
            upDiv.style.zIndex = this._backgroundElement.style.zIndex + 1;
        }

Finally we have to setup the handlers for the beginRequest, endRequest and pageLoaded

        // register the beginRequest and endRequest events using the AjaxToolbox.BehaviorBase            
        this.registerPartialUpdateEvents(); 
        this._pageLoadedHandler = Function.createDelegate(this, this._pageLoaded);
        this._pageRequestManager.add_pageLoaded(this._pageLoadedHandler);

Now all that is left to do is, on the beginRequest, position and size the background div above the targetControl, show it and show and center the "Loading..." div; and hide everything on the endRequest event. The problem is that if we have two updatepanels on our page we'll still get called even if our targetControl is not being updated, so we will use the sender parameter of the beginRequest to determine the source of the postback, then we will try to $get our targetControl within the child control tree of the panel that is updating and if we get something back, it means that we are part of the updating panel and we need to disable our targetControl. The beginRequestHandler goes something like:

        // find out who cased the postback and if the element is a child of the update panel
        var upId = this.getUpdatingElement(sender);
        // the ip comes as a UniqueID and we need to compare it the ControlID (you know the ctl001$myctl vs ctl001_myctl problem)
        var callingId = null;
        if( upId != null )
            callingId = upId.replace(/\$/g,'_');
        // only do all this if the element is part of the updaing panel
        var element = $get(this.get_element().id, $get(callingId));
        if(  element == null )
            return ;
            
        // so we are part of the update... show our panels

Now we just need to show and position our divs

        var progressDiv = $get(this._loadingDiv);
        if( typeof(progressDiv) != 'undefined' )
        {
            // is important to show it before we get the bounds, otherwise, we'll get 0,0 for the width and height
            progressDiv.style.display = 'block';
            
            var progBounds = Sys.UI.DomElement.getBounds(progressDiv);
            // now center the progress div onto the control
            var x = bounds.x + Math.round( ((bounds.width - progBounds.width) / 2));
            var y = bounds.y + Math.round( ((bounds.height - progBounds.height) / 2));
            Sys.UI.DomElement.setLocation (progressDiv, x, y);        
        }
        // show the div that covers the element
        this._backgroundElement.style.width = bounds.width + 'px'; // important to add the 'px' for fireFox
        this._backgroundElement.style.height = bounds.height + 'px';
        Sys.UI.DomElement.setLocation (this._backgroundElement, bounds.x, bounds.y);
        this._backgroundElement.style.display = 'block';

And that's it, in the endRequest we just hide everything and we are done.

        if( typeof(this._loadingDiv) != 'undefined') {        
            var progressDiv = $get(this._loadingDiv);
            progressDiv.style.display = 'none';
        }
        else
            alert('cant find progress div');
        this._backgroundElement.style.display = 'none';

 

As you can see, we just have to add some checks here and there to be able to handle multiple UpdatePanels on the same page. Hope this helps someone save some coding time.

To download the sample with the extender click here

1 Comment

Comments have been disabled for this content.