UpdatePanel control is used for different purposes like reducing flickering of the page and reducing network traffic generated by a web site. Developers often wrap input boxes into an UpdatePanel to implement cascading drop-downs and update other related controls. Cascading drop-downs can be implemented with a help of Ajax Control Toolkit control extenders, but in general case you will either need to write script code or place controls in an update panel. The latest approach is very easy to implement, but it also has a lot of drawbacks. If you wrap input boxes (and other input controls) in the update panel, you must be aware about the following consequences:
- If a user changes value of an auto post-back control and "tabs" to the next control, he or she starts asynchronous (AJAX) post-back. However, as the post-back is asynchronous the user can continue typing (selecting value) in the next selected control. If the server responds to asynchronous post-back after the user has started typing in the next control, everything typed in this control is lost once AJAX post-back completes.
If the server responds quite fast, this situation is unlikely to happen, but if the server is busy or web site is browsed from the dial-up connection, it is likely to happen. It is still less probable than with full-post back, but possible.
- The second annoying behavior you may experience with input controls inside an UpdatePanel control is lost focus. Input (keyboard) focus moves to nowhere once AJAX post-back completes. Web site users who prefer to use keyboard need to use mouse to activate appropriate input box or press TAB multiple times. (The exact behavior is a little different for Internet Explorer and Mozilla FireFox).
You can place this UpdatePanel to an ASPX page to see the second problem by yourself:
<asp:UpdatePanel runat="server" ID="up">
<ContentTemplate>
<div>
<asp:DropDownList ID="ddl1" runat="server" AutoPostBack="True">
<asp:ListItem>1</asp:ListItem>
<asp:ListItem>2</asp:ListItem>
<asp:ListItem>3</asp:ListItem>
</asp:DropDownList></div>
<div>
<asp:DropDownList ID="ddl2" runat="server" AutoPostBack="True">
<asp:ListItem>1</asp:ListItem>
<asp:ListItem>2</asp:ListItem>
<asp:ListItem>3</asp:ListItem>
</asp:DropDownList></div>
<div>
<asp:TextBox runat="server" ID="tb"></asp:TextBox><br />
</div>
</ContentTemplate>
</asp:UpdatePanel>
Why Does It Happen?
I need to remind some details of how UpdatePanel works to answer this question.
If ScriptManager EnablePartialRendering property is set to true, controls on the web page can initiate asynchronous post-back. When it happens web page send asynchronous request to the server. The web page instance at the server goes through all the phases in its normal lifecycle, but instead of full rendering only partial rendering happens. The html content for updated panels is sent back to the client in the format that client script manager control can recognize. The client part of the ScriptManager control parses the response and set innerHTML properties of all DIV elements generated by UpdatePanels.
So, what happens to the UpdatePanel with input controls? When client script receives a response from the server with new Html content for the UpdatePanel it assigns this content to the DIV element generated by the UpdatePanel. Input focus at this time is in one of the controls inside the DIV element (UpdatePanel). The browser destroys the old content of the UpdatePanel including input controls and creates new elements by parsing new content assigned to the innerHTML property. Input focus moves out of the update panel when focused input controls is being destroyed and it is not restored later when the new controls are created from the assigned html.
Solution of the Problem with the Lost Input Focus
The basic idea behind the solution is to save the ID of the control with input focus before the update panel is updated and set input focus back to that control after the update panel is updated.
I come with the following JavaScript which restores the lost focus in the update panel.
var lastFocusedControlId = "";
function focusHandler(e) {
document.activeElement = e.originalTarget;
}
function appInit() {
if (typeof(window.addEventListener) !== "undefined") {
window.addEventListener("focus", focusHandler, true);
}
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading(pageLoadingHandler);
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoadedHandler);
}
function pageLoadingHandler(sender, args) {
lastFocusedControlId = typeof(document.activeElement) === "undefined"
? "" : document.activeElement.id;
}
function focusControl(targetControl) {
if (Sys.Browser.agent === Sys.Browser.InternetExplorer) {
var focusTarget = targetControl;
if (focusTarget && (typeof(focusTarget.contentEditable) !== "undefined")) {
oldContentEditableSetting = focusTarget.contentEditable;
focusTarget.contentEditable = false;
}
else {
focusTarget = null;
}
targetControl.focus();
if (focusTarget) {
focusTarget.contentEditable = oldContentEditableSetting;
}
}
else {
targetControl.focus();
}
}
function pageLoadedHandler(sender, args) {
if (typeof(lastFocusedControlId) !== "undefined" && lastFocusedControlId != "") {
var newFocused = $get(lastFocusedControlId);
if (newFocused) {
focusControl(newFocused);
}
}
}
Sys.Application.add_init(appInit);
If you save this code to FixFocus.js file, it can be used as:
<asp:ScriptManager ID="c_scriptManager" runat="server">
<Scripts>
<asp:ScriptReference Path="~/FixFocus.js" />
</Scripts>
</asp:ScriptManager>
Unfortunately, different browsers handle input focus a little differently. Mozilla FireFox browser does not provide an easy way to get currently focus element at all. The script, therefore, handles these differences between browsers.
You may find the focusControl function a little strange. I cannot explain the magic it does, but this is really required to set focus to the control in the Internet Explorer. ASP.NET AJAX extensions use this code itself when setting focus set by ScriptManager.SetFocus method.
I tested this code with Internet Explorer 7 and the most recent build of Mozilla FireFox.
Sample Web Site
You can download the sample web site here.
UPDATE
You can often solve the problem by placing each input control into its own update panel. However, this approach does not solve the problem if user press TAB to go to the next control, which is in its own UpdatePanel.
The code above is a workaround. If you need to implement cascading drop-downs it is better to go with Ajax Control Toolkit Cascading Drop-Down Control Extenders. If you need to use UpdatePanel or several UpdatePanels use this code, but be aware of problem #1 which is not solved by this script.