Friday, July 15, 2011

UpdatePanel animations and focus

My site at mypeoplematter makes extensive use of UpdatePanels to provide a more pleasant user experience. For pages that submit data to the database (and might therefore take some time to return), I use UpdatePanelAnimationExtenders to give a visual indication that something is happening. So far, so good. My pages don't flash, and the user knows when they have to wait.

I have a page for entering weekly donations. Most churches do this in a large batch, so the page is designed to be very easy to use: the tab order is very predictable, you can press the Enter key when you have entered all the necessary information, etc. Unfortunately, when I add an UpdatePanel and the AnimationExtender to this page, I lost a critical usability feature: the page did not set the focus to the proper control.

This is one of those problems that is well documented on the web, but not well solved. I found my answer here, in the midst of a lot of incorrect suggestions. Whet I have done is added an AnimationExtender to the TextBox that I want to have control. That AnimationExtender uses the OnLoad animation to set the focus. It's a little kludgy, it would be nice if the UpdatePanelAnimationExtender did it correctly in the first place.

But what's really nice is that I was able to hide the code in a custom control I had already written. The UpdatePanelAnimationExtender is complex to specify, and requires some javascript to make it all work. I had already encapsulated all of that into a composite control that I could just drop onto my page thus:

<mypeople:UpdateAnimation ID="updateAnimation" runat="server" AnimationTargetID="wholePage" UpdatePanelID="upnlContent" FocusControlID="donorLookup" />

I added the FocusControlID attribute, and added some code to generate the necessary controls and javascript:

protected override void CreateChildControls() {
 ... other code for the update panel

 // If there is a FocusControlID, add an extender to set the focus
 focusAE = null;
 if (!String.IsNullOrEmpty(FocusControlID)) {
  UpdatePanel upnl = Page.MuchBetterFindControl(UpdatePanelID);
  focusAE = new AjaxControlToolkit.AnimationExtender {
   TargetControlID = FocusControlID, 
   Animations = "",
 } // if

 ... code to build the page
} // CreateChildControls

protected override void OnPreRender(EventArgs e) {
 if (!String.IsNullOrEmpty(FocusControlID)) {
  Control theControl = NamingContainer.FindControl(FocusControlID);
  String controlID = FocusControlID;
  if (theControl != null) {
   if (theControl is PartyLookup) {
    controlID = ((PartyLookup)theControl).TextBoxClientID;
   } else {
    controlID = theControl.ClientID;
   } // if
  } // if
  js += "\nfunction OnLoad_FocusField() { UpdateExtenderFocusField('" + 
        controlID + "'); }";
 } // if
} // OnPreRender

The method MuchBetterFindControl is from here. It searches the hierarchy of control for the control with the indicated ID.

An interesting note: I cannot create the UpdatePanelAnimationExtender inside the UpdatePanel, but I have to create the focus control's AnimationExtender inside the UpdatePanel. So, in the code for CreateChildControls, I make sure to add the extender to the UpdatePanel's controls, not to those of the composite control.

All in all, this turned out to be a nifty, relatively clean solution to a problem that remains unsolved in most of the places I saw it on the Internet. If you would like the code, email me and I will bundle up the pieces for you.

No comments: