Get Focused - 30 Oct 2009

Set the input focus to controls programmatically.

LANGUAGES: C# | JavaScript

TECHNOLOGIES: Inheritance | Page Class | Client-side Script

ASP.NET comes with a full bag of goodies to help developers simplify or even eradicate rather annoying tasks. But ASP.NET can't take care of everything. For example, have you ever wanted to programmatically assign the input focus to a particular control displayed on the page? To do so, you must write some code yourself because, at least in this case, ASP.NET has no built-in facilities for it.

Old School: The JavaScript Way

Web pages full of interactive controls - text boxes, in particular - are easier to use when one of the constituent controls is given the focus as the page shows up. Unfortunately, you can set the focus only by using client-side script code. In addition, you must write this script code in JavaScript to preserve browser compatibility and, more importantly, you must make minimal assumptions on the underlying browser object model. (So forget about fancy things such as Internet Explorer behaviors.) Consider a simple HTML page that displays automatically with the input focus on the second input field:

 

  

  

 

The page embeds a small script that calls the focus method on the second control in the first form. The document.forms expression returns the collection of the forms defined in the page. The first index refers to the form, and the second indicates the position of the control in the selected form. Both indexes are zero-based and could be replaced by the value of the element's name attribute. For example:

 

if(document.forms[0]["lname"] != null)

   document.forms[0]["lname"].focus();

 

The trick works with any version of Internet Explorer and with Netscape versions 3.0 and later. If you plan to use this within the ASP.NET environment, the form index must be zero because ASP.NET pages have exactly one form element.

 

To recap: To assign the input focus to a particular control programmatically, you embed a small and rather inflexible JavaScript function and set the onload attribute of the page's body tag with whatever name the aforementioned JavaScript function has. So far so good, but how can you import this into ASP.NET projects?

Register Client-side Scripts

Nothing prevents you from simply cutting and pasting that JavaScript function into your next ASP.NET page, but there might be a better and more systematic way. The Page object has a couple methods with promising names: RegisterClientScriptBlock and RegisterStartupScript. Both methods inject a block of script code into the target ASP.NET page. The former inserts the script immediately after the opening tag of

; the latter, on the other hand, emits the script immediately before the form's closing tag.

 

If you register code that runs with RegisterClientScriptBlock (not simply a function declaration), it runs as soon as it's encountered, before the actual form's initialization. Therefore, the code cannot access any of the form's elements because, at that time, the elements haven't been instantiated yet. Conversely, if you use RegisterStartupScript, any usable code still runs when encountered, but this happens only when the form has been initialized. It's just right for a startup script. Here's how to use both methods:

 

RegisterStartupScript("InputFocusHandler", jscriptCode)

RegisterClientScriptBlock("InputFocusHandler", jscriptCode)

 

Calling either method does not insert the JavaScript script code physically into the page's HTML source code. The code, in fact, is generated only as the final step in the ASP.NET page-building process. So what happens to the scripts? Look at the signature of both methods.

 

The latter argument is the string that contains the script code; the former simply names the script. Actually, the script code is parked temporarily into dedicated and case-sensitive name and value collections. The name identifies the script, and the value defines it. Each registering method runs its own collection, so script names must be unique only within the scope of the method. The content of script collections is flushed into the page output when the HTML code for the page is created. You must wrap each piece of script code with opening and closing script tags like this:

 

StringBuilder sb = new StringBuilder("");

sb.Append("

Figure 1. The script declares and then invokes the function that sets the focus to the specified HTML input control.

 

For your convenience, you might want to keep the function declaration and the actual call in different script blocks. For instance, you can use RegisterClientScriptBlock to declare the function and RegisterStartupScript to register the call. This approach results in more elegant code, but it also requires the Page object to handle two non-empty collections instead of only one. This isn't a big concern, but when you have to squeeze out every last bit of performance, it could make a difference. Script collections are initialized only when the first item is added (see the sidebar, "Hybrid is Better," for more information on how script collections are managed).

 

Figure 2 demonstrates a sample ASP.NET page viewed with Internet Explorer 6.0 and Netscape 4.6. Both browsers handle the focus correctly.

 


 


Figure 2. Here is the sample page seen through Internet Explorer 6.0 (top) and Netscape 4.6 (bottom).

 

The handling of focus does not conflict with the behavior of the page when tab indexes have been set through the input controls' TabIndex property. If you give focus to a control programmatically and then press the  Tab key, the next control to receive the focus is simply the next one in the chain.

 

Extend the Page Class

Although the solution is fairly clean and relatively unobtrusive - you simply add a standard piece of code - it still requires you to enter changes into working code. Can all the necessary changes be buried into some sort of compiled code and never show up in the ASP.NET sources? Can you toggle this feature on and off in a declarative way?

One feature that makes ASP.NET unique among server-side Web-development technologies is the code-behind feature. It often is described as the programming technique that allows you to obtain a neat separation between the page's code and layout. The key aspect of the code-behind feature, however, is the possibility to change the base class for the current ASP.NET page in a declarative manner. The base class defaults to Page, but you can change it by using the Inherits attribute of the @Page directive:

 

<%@ Page Language="C#" Inherits="Expoware.PageWithFocus" %>

 

This code declares that the current page inherits from the Expoware.PageWithFocus class rather than the base class Page. You can indicate the location of the desired class using the Src attribute. If you don't include an Src attribute, ASP.NET will assume the class is contained in one of the referenced assemblies.

Now, try creating a new class that inherits from Page. Make the class run the code you saw earlier for handling the input focus. Once the class has been compiled into an assembly, you make it available to the ASP.NET application by deploying it into the bin folder below the virtual directory. No other changes will be required except those needed to specify, case by case, what the focused control is. Here's a closer look at the PageWithFocus class:

 

public class PageWithFocus : Page

{

   public PageWithFocus()  {}

 

   private const string FUNCTION_NAME = "__setFocus";

   private const string SCRIPT_NAME = "__inputFocusHandler";

 

   public string Focus;

 

   protected override void OnPreRender(EventArgs e)

   {

       base.OnPreRender(e);

      AddStartupScript();

   }

}

 

The class features a string property named Focus, which contains the name of the control getting the input focus. In addition, the class overrides the OnPreRender default event handler to make it add the startup script code. OnPreRender is the event you handle when you need to perform any updates before the output is rendered to the browser. The AddStartupScript method prepares the string of JavaScript code discussed earlier and registers it with the page. The name of the JavaScript function and the name of the script are constant in the page's implementation, but you can change them at will and even expose them programmatically through properties.

In the ASP.NET page, you move the input focus to a particular control, using this code:

 

this.Focus = "user";

 

The equivalent Visual Basic .NET code is:

 

Me.Focus = "user"

 

Notice, however, that you can omit the prefix "this" (or "Me") because setting the Inherits attribute guarantees that the type of the current page, to which this or Me refers, is PageWithFocus:

 

Focus = "user";

 

Any attempt to access the Focus property through the Page property generates a compiler error. In fact, the Page property references an object of type Page, which has no notion of the Focus property.

The Focus property is defined as a string, but setting it to a quoted number (for example, "4") would work as well, and it would give you a great chance to indicate the input focus by position rather than by name. If you plan to specify the control by index, bear in mind that each ASP.NET page comes with at least three hidden input fields: EVENTTARGET, EVENTARGUMENT, and VIEWSTATE. So the lowest index for page-specific input controls is 3.

 

Exceptions to the Rule

To top off this article, let me analyze a couple special cases. When you use editable DataGrid controls, you don't know the name of the input controls being used unless you use template columns. In this case, you might want to resort to position-based focus. The zero-based index for the DataGrid control's first text box is greater than two and increased by one for each input control that precedes it in the page.

What if your page includes user controls? A user control is an aggregate of child controls that appear as one monolithic component. Constituent controls are not accessible from the page programmatically, and you might not know about their internal names or IDs. In addition, notice that nested controls in ASP.NET have a composed and colon-separated name:

 

loginform:txtLastName

 

The name in that code belongs to a text-box control whose name is txtLastName. The text box is contained in a user control named loginform. If you know this piece of information, pass it along to the Focus property as is:

 

Focus = "loginform:txtLastName";

 

Otherwise, add a new method (such as GetNameOf) to your user control and make it return the exact ID for a constituent control:

 

Focus = loginform.GetNameOf("LastName");

 

As with the Focus property, the GetNameOf function (or whatever you name it) can be contained in a custom user-control class from which your actual user controls would inherit.

 

The code referenced in this article is available for download.

 

Dino Esposito is a trainer and consultant for Wintellect (http://www.wintellect.com), where he manages the ADO.NET class. Dino is the author of Building Web Solutions with ASP.NET and ADO.NET and the upcoming Applied XML Programming for .NET from Microsoft Press. He also is a co-founder of http://www.VB2TheMax.com. E-mail him at mailto:[email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

Hybrid is Better

Script collections are managed through a special type of collection: the HybridDictionary class. This class implements the methods of the IDictionary interface in a flexible way. It uses a ListDictionary object as long as the number of items in the collection is relatively small. When the size of the dictionary increases beyond an internal threshold, the class switches automatically to a Hashtable object, which happens to work more efficiently for large collections.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish