Click here to Skip to main content
Click here to Skip to main content

Microsoft AJAX: Persist focus, prevent lost requests, and avoid click happy users

, 15 Sep 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
How to extend the behavior of async postbacks for update panels.

Introduction

When attempting to create web applications that are based off Windows applications, there tends to be a thin line between behavioral differences that is acceptable to end users or the management. There are more than a few examples out on the web right now that will show you how to queue async requests and/or persist focus between requests, but I want to encapsulate all this logic into a nice JavaScript object to hide the functionality from my own business objects, for extensibility reasons. I have to admit that some of the ideas here and there came from separate posts, but I no longer have the links. If anybody happens to find a link that shows similar code, I will quote it here.

Objective

To create a JavaScript object that will encapsulate the pageRequestManager of Microsoft AJAX in order to extend its behaviors when handling async postbacks for an UpdatePanel.

The Code

Object Creation

The first step is to create the JavaScript object and initialize it by placing its constructor call at the top of the .js file (this ensures I don’t have to worry about constructing it outside of the file itself).

var _AsyncPostbackUtility = new AsyncPostbackUtility();
function AsyncPostbackUtility(){}

Constructor

Afterwards, I need some logic in the constructor that will wire to the events of the pageRequestManager so that I can interact with it during postbacks.

Notes: I check to see if Sys is defined (i.e., if there is a scriptManager on the page and has the scriptManager had a chance to be defined). If you have included this script in the scripts directory of the script manager, you are fine, but things can go south if you created your own include tag within your HTML page directly.

function AsyncPostbackUtility(){ 
    var currentFocusedControlId = ""; //the last control that had focus. 
    var requestQueue = new Array(); //a pending request queue. 
    if (Sys) 
        Sys.Application.add_init(appInit); 
    else 
        throw 'Sys is not defined, please verify you ' + 
              'have a script manager on the page ' + 
              'and that this object js file is included ' + 
              'in its script collection.'; 

function appInit(){ 
    Sys.WebForms.PageRequestManager.getInstance().
        add_initializeRequest(initializeRequestHandler); 
    Sys.WebForms.PageRequestManager.getInstance().
        add_pageLoading(pageLoadingHandler); 
    Sys.WebForms.PageRequestManager.getInstance().
        add_endRequest(endRequestHandler); 
    } 
    …
}

Event Handlers

Now that we have object creation and event wiring going, it's time to place some logic into the event handlers themselves in order to achieve the functionality we are looking for.

Initialize Request Handler

This is the first event that gets called whenever a postback request is made by the client. Within this event, we have the ability to cancel the request, which is what we will utilize if we are already in an async postback. You might be asking yourself why we need a queue, and the answer is simple. If a request is attempted while another request is pending, the first request on the page is canceled by the client in favor of the second request, which, as you can imagine, might leave your UI a bit out of sync. Take notice that we maintain a reference to the element that caused the last postback, and that we only queue requests that are not generated by that element. This is done to prevent click happy users when an async postback is taking longer than expected, and disabling the UI or using a progress indicator isn’t useful. By queuing the requests, we can give the application a more fluid feel without disabling the form on every postback, and ensure we don’t miss updates from the UI in case the user is moving quickly and triggering postbacks.

function initializeRequestHandler(sender, args){ 
    var postBackElement = args.get_postBackElement(); 
    if (Sys.WebForms.PageRequestManager.getInstance().
                     get_isInAsyncPostBack() == false){ 
        executingElement = postBackElement; 
    } 
    else{ 
    if (executingElement != postBackElement){ 
        var evArg = $get("__EVENTARGUMENT").value; 
    Array.enqueue(requestQueue, new Array(postBackElement, evArg)); 
    } 
    args.set_cancel(true); 
    } 
}

Page Loading Request Handler

This event is fired after the page request manager gets the result of the async postback, but before it has rebuilt the UpdatePanel, and is used exclusively by this object to get a reference to the ActiveElement object to persist focus once the UpdatePanel is rebuilt.

Note: Firefox 3/IE6 and above will support the activeElement property. If you must support different or earlier versions of the browsers, I would recommend a different method at this point.

function pageLoadingHandler(sender, args){ 
    currentFocusedControlId = typeof(document.activeElement) == 
                              "undefined" ? "" : document.activeElement.id; 
}

End Request Handler

This event is fired after the UpdatePanel has been loaded and the async postback has completed. This is where we will persist focus and check our queue to see if we need to handle additional postbacks.

function endRequestHandler(sender, args){ 
    focusControl(); 
    if (requestQueue.length > 0){ 
        var elemEntry = Array.dequeue(requestQueue); 
        var _element = elemEntry[0]; 
        var _eventArg = elemEntry[1]; 
        Sys.WebForms.PageRequestManager.getInstance()._doPostBack(
                                          _element.id, _eventArg); 
    }
}

Focus Control Method

The focus control method is straightforward except for the trick at the end to reset the cursor to the end of a text box or a text area element.

function focusControl(){ 
    if (typeof(currentFocusedControlId) != 
               "undefined" && currentFocusedControlId != ""){ 
        var targetControl = 
            document.getElementById(currentFocusedControlId); 
    if (targetControl.contentEditable != "undefined"){ 
        oldContentEditableSetting = targetControl.contentEditable; 
    
        targetControl.contentEditable = false; 
    } 

    targetControl.focus(); 
    if (oldContentEditableSetting != "undefined") 
        targetControl.contentEditable = oldContentEditableSetting; 
        targetControl.value = targetControl.value; 
    } 
}

How to Use the Code

As it comes out of the box, it is very simple. Just add a script reference via the script manager on your page to the .js file of this article, or you could remove the constructor from the .js file and call it somewhere after the body load event of your web page.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

MCF.Goodwin
Software Developer
United States United States
Ah, just another dev living the life of Dilbert. Smile | :)

Comments and Discussions

 
GeneralJS error on pages w/o update panels PinmemberNotVirg30-Jun-10 3:03 
GeneralValue of control not preserved when modified in Page Request handler Pinmember2999AD1-May-09 2:08 
GeneralModal PopUp loss Focus Pinmemberjainkhyati16-Mar-09 23:06 
GeneralNice one. Pinmemberpg240617-Nov-08 4:29 
GeneralInclude the script using ScriptManager - Script PinmemberMember 43845137-Oct-08 3:07 
GeneralAlmost there... but focus isn't set correctly Pinmemberperraw24-Sep-08 0:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141022.2 | Last Updated 15 Sep 2008
Article Copyright 2008 by MCF.Goodwin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid