Wednesday, February 24, 2010

Keeping focus on AJAX elements in an update panel

In a recent project a had to create an updatepanel in ASP.NET.
The creation of the panel was not the problem, the panel had a couple of textboxes and when the user 'tabs' between them, then a server-call was done to validate the input through a service.

It was an address-validation control. User inputs his address and it is checked on the fly against a service.

The problem was that when the user tabs, and the AJAX callback came back, then the focus of the next selected element was lost, resulting in irritating behavior that the user had to click with his mouse to the next field.

After a lot of searching I came up with a solution.

"Record which element has focus and put the focus back after the AJAX library destroyed the panel and rebuilds all the controls."
Let's jump into code:
- First I had to register a little script in the OnInit function of the ASP.NET Page:

protected override void OnInit(EventArgs e)

string scriptToRegister = "function SaveFocus(fieldId) { lastField"
+ UpdatePanel1.ClientID + " = fieldId; }

ScriptManager.RegisterClientScriptBlock(this, typeof(UserControl), "SaveScriptAddress", scriptToRegister, true);

Then I had to add this script as an attribute to all the textboxes in the updatepanel, that needed the focus. Those elements get updated when the ajax call returns.

protected void Page_Load(object sender, EventArgs e)
if (!IsPostBack)
// onfocus=""
txtBoxNumber.Attributes.Add("onfocus", "SaveFocusAdv('" + txtBoxNumber.ClientID + "')");
txtHouseNumber.Attributes.Add("onfocus", "SaveFocusAdv('" + txtHouseNumber.ClientID + "')");
txtZipcode.Attributes.Add("onfocus", "SaveFocusAdv('" + txtZipcode.ClientID + "')");
txtddlStreets.Attributes.Add("onfocus", "SaveFocusAdv('" + txtddlStreets.ClientID + "')");
txtddlCities.Attributes.Add("onfocus", "SaveFocusAdv('" + txtddlCities.ClientID + "')");

The C# coding is done at this point. All I had to do next was inserting a little javascript snippet in the ASP.NET Usercontrol markup:
This script was placed outside of the updatepanel! Because it may not be destroyed when the partial rendering kicks in.
Put the following code between a script tag:

var lastField<%= UpdatePanel1.ClientID %>;
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler<%= UpdatePanel1.ClientID %>);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler<%= UpdatePanel1.ClientID %>);

function BeginRequestHandler<%= UpdatePanel1.ClientID %>(sender, args) { = 'wait';
function EndRequestHandler<%= UpdatePanel1.ClientID %>(sender, args) { = 'default';
if(lastField<%= UpdatePanel1.ClientID %> != undefined)
setTimeout('DoFocusOnLastElement(lastField<%= UpdatePanel1.ClientID %>)', 1);
function DoFocusOnLastElement( fieldId )
var element = document.getElementById(fieldId);
if(element != null) element.focus();

That's it, now the 'tabbing' will occur normally, like nothing else happens....
When the service takes a little longer then expected, then the user input is replaced by the content that the service sends back (like a city name) but that's not really a probem.
I hope this can be usefull to someone ;)