Click here to Skip to main content
15,891,375 members
Articles / Web Development / XHTML
Article

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

Rate me:
Please Sign up or sign in to vote.
3.75/5 (6 votes)
15 Sep 2008CPOL4 min read 41.1K   240   12   6
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).

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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)


Written By
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 Pin
NotVirg30-Jun-10 3:03
NotVirg30-Jun-10 3:03 
GeneralValue of control not preserved when modified in Page Request handler Pin
2999AD1-May-09 2:08
2999AD1-May-09 2:08 
GeneralModal PopUp loss Focus Pin
jainkhyati16-Mar-09 23:06
jainkhyati16-Mar-09 23:06 
GeneralNice one. Pin
pg240617-Nov-08 4:29
pg240617-Nov-08 4:29 
GeneralInclude the script using ScriptManager - Script Pin
Member 43845137-Oct-08 3:07
Member 43845137-Oct-08 3:07 
GeneralAlmost there... but focus isn't set correctly Pin
perraw24-Sep-08 0:05
perraw24-Sep-08 0:05 

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

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