CoreCoder
LANGUAGES: VB.NET
ASP.NET VERSIONS: 2.0
Hit Enter and Post Back Revisited
Build a Custom Postback Control in ASP.NET 2.0
By Dino Esposito
In the February 2004 issue of asp.netPRO, I wrote an article to demonstrate an enhanced version of the TextBox control that captures the Enter key and posts back (see Hit Enter and Post Back). Most of the time, ASP.NET pages post back through user clicks either submit buttons or link buttons. In other cases, if the auto-postback feature is turned on, some list controls (e.g., ListBox and DropDownList) may post back as the user changes the current selection. In general, the postback event can be triggered by any event that can be detected on the client, including drag-and-drop, mouse movements, keyboard activity, and timeouts. You can build custom controls around any of these HTML events and manage to expose related server-side events.
In this article, I ll revisit the source code of the PostTextBox control to obtain a new control that provides the same set of features but is specifically designed for ASP.NET 2.0. For a postback control, being specifically designed for ASP.NET 2.0 primarily means one thing: supporting cross-page postbacks.
Posting with the TextBox Control
First off, it is interesting to recall that if you create a sample page that contains exactly one TextBox control (plus any other combination of server controls), give focus to the TextBox, then hit Enter, the page posts back automatically and without any efforts of your own. If you add a second TextBox control (or even a client input text field) to the page, the magic effect ends and the page won t post back until you click a submit button.
End users, especially those who grew up with non-Windows computers and who may not yet be accustomed to using the mouse, may find hitting the Enter key a much more natural and faster way to close the form and submit some data. In Web pages, however, the idea of posting data to a server is naturally associated with the idea of clicking a button. In ASP.NET 2.0, this relationship has been formalized by defining a new interface IButtonControl. The interface defines the members that ASP.NET expects to find in server controls that post back. To be precise, not all ASP.NET 2.0 controls that have the ability to post back actually implement the interface. The only exception, though, are list controls that post back only when their AutoPostBack property is set to true. Let s learn more about the IButtonControl interface and how to implement it in a custom TextBox control.
The IButtonControl Interface
One of the goals of the IButtonControl interface is refactoring a portion of the existing control functionality into more homogeneous subsets of members. The IButtonControl interface defines the properties and events that characterize a control that acts like a button on a Web page. Similarly, the interfaces ITextControl and IEditableTextControl group properties and events common to all controls that display, and optionally edit, a text. Implementing any of these interfaces doesn t make the control necessarily richer in terms of functions, but certainly more readable and easier to extend and customize. The following code snippet shows the declaration of the IButtonControl interface:
Public Interface IButtonControl
' Events
Event Click As EventHandler
Event Command As CommandEventHandler
' Properties
Property CausesValidation As Boolean
Property CommandArgument As String
Property CommandName As String
Property PostBackUrl As String
Property [Text] As String
Property ValidationGroup As String
End Interface
The table in Figure 1 details the various members. As you can see, the interface lists properties that relate to the action of posting back. The events, though, are those typical of button controls and don t perfectly map (at least as far as names are concerned) to a TextBox control even a TextBox that behaves and posts back like a button.
Property |
Description |
CausesValidation |
Indicates whether validation is performed when the control posts back. |
CommandArgument |
Gets or sets an optional parameter passed to the control s Command event along with the associated CommandName. |
CommandName |
Gets or sets the command name associated with the control. |
PostBackUrl |
Indicates the URL that will handle the postback triggered through the button control. |
Text |
Gets or sets the text displayed by the control. |
ValidationGroup |
Gets or sets the name of the validation group to which the control belongs. |
Visible |
Indicates whether the control will be rendered or kept hidden. |
|
|
Event |
Description |
Click |
Event raised when the control posts back. The event passes no information to the page. |
Command |
Event raised when the control posts back; more specific than, and subsequent to, Click. The event passes command name and argument to the page event handler. |
Figure 1: The IButtonControl interface.
The PostTextBox Control
The PostTextBox control is a regular TextBox control except that it posts back if the user hits Enter when the control holds the input focus. Ideally, one would design such a control as shown in the following code snippet:
Public Class PostTextBox
Inherits WebControl
Implements IButtonControl,
IPostBackDataHandler,
IEditableTextControl,
ITextControl
This is exactly the signature of the TextBox class plus the IButtonControl interface. If you opt for this approach, though, be prepared to rewrite much of the code that the ASP.NET team has hardcoded in the implementation of the native TextBox control. In addition, a conflict would arise between the members of the ITextControl and IButtonControl interface. Both, therefore, have a method named Text of type string. The conflict can be easily removed by dropping the ITextControl interface. The interface, in fact, features only the Text member, which is already part of the control if the IButtonControl interface is supported.
A better solution might be inheriting the PostTextBox from TextBox:
Public Class PostTextBox
Inherits TextBox
Implements IButtonControl
The interfaces IPostBackDataHandler, IEditableTextControl, and ITextControl are not explicitly listed because they are implemented by the base class. The conflict on the Text property is not eliminated; in addition, other conflicts arise on the other two properties in the IButtonControl interface: ValidationGroup and CausesValidation. Both properties are exposed by the TextBox class.
You can remove any conflicts by using the Shadows keyword (new in C#) to shadow the inherited member and redefine it as a member of the implemented interface. In terms of implementation, you can always refer to the base member using the MyBase keyword in Visual Basic .NET (and base in C#). Listing One shows the implementation of the IButtonControl in the PostTextBox control.
The PostTextBox control outputs an INPUT field whose onkeypress event is bound to a piece of JavaScript code. The JavaScript code captures any keystroke; if the key code equals 13 (the Enter key), a regular postback is made. Figure 2 shows the server code that adds proper client script to the PostTextBox control.
Private Sub AddBehavior()
RegisterEnterKeyHandler()
Attributes("onkeypress") = "CaptureEnter(this)"
End Sub
Private Sub RegisterEnterKeyHandler()
' Build the Javascript code to capture the Enter key
Dim builder As New StringBuilder
builder.AppendLine("function CaptureEnter(ctl) {")
builder.AppendLine(" var key = window.event.keyCode;")
builder.AppendLine(" if (key == 13) {")
builder.AppendLine(" __doPostBack(ctl.name, '');")
builder.AppendLine(" return;")
builder.AppendLine(" }")
builder.AppendLine("}")
' Register the script
Dim js As String = builder.ToString()
If Not Page.ClientScript.IsClientScriptBlockRegistered( _
"CaptureEnter") Then
Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), _
"CaptureEnter", js, True)
End If
' Ensure that __doPostBack is declared in the page
Page.ClientScript.GetPostBackEventReference(Me, "")
End Sub
Figure 2: Adding client script to the PostTextBox control.
The AddBehavior method is invoked from within the OnLoad override while the control is being loaded in the request processing:
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
AddBehavior()
End Sub
At this point, you can place any number of PostTestBox controls in a page, move the input focus to any of them, hit Enter, and, voil , the page posts back. This is not enough, though, to fire a server event that the page author can handle to implement some logic. To add a postback event handler, a control implements yet another interface, IPostBackEventHandler:
Public Interface IPostBackEventHandler
Sub RaisePostBackEvent(ByVal eventArgument As String)
End Interface
Figure 3 shows the typical ASP.NET 2.0 implementation of the interface. First you set the control to validate that the event originated from the user interface that was originally rendered by the same control. In this way, the control will not respond to any spoofed event notification. The ValidateEvent on the page s ClientScript object does the trick. Next, if validation is enabled, you call the Validate method on the page specifying the validation group to which the PostTextBox control belongs. Finally, you fire the two server events in the IButtonControl interface: Click and Command.
Public Sub RaisePostBackEvent(ByVal eventArgument As String) _
Implements IPostBackEventHandler.RaisePostBackEvent
Page.ClientScript.ValidateEvent(UniqueID, eventArgument)
If CausesValidation Then
Page.Validate(ValidationGroup)
End If
OnClick(EventArgs.Empty)
OnCommand(New CommandEventArgs(CommandName, CommandArgument))
End Sub
Figure 3: Implementation of the IPostBackEventHandler interface.
Note that you re not forced to implement IButtonControl; if you don t implement the interface but still bring in nearly the same members, you can rename the Click event to a more intuitive EnterPressed or Posted, or whatever else you like.
Figure 4 shows a sample page with two PostTextBox controls. The second textbox is bound to a regular expression validator; when you hit Enter, the validation occurs and prevents the page from posting back in case of errors.
Figure 4: Sample page in action.
Cross-page PostBack
As mentioned, the PostTextBox control implements the IButtonControl interface, which includes the PostBackUrl property. The property is used to perform cross-page postbacks and indicates the URL to post to from the current page when the Enter key is pressed. When PostBackUrl is used, the JavaScript code to trigger the postback changes. To obtain the cross-page postback JavaScript code, you call a different overload of the same GetPostBackEventReference method you use for regular postbacks:
Dim options As New PostBackOptions(Me)
options.ActionUrl = PostBackUrl
postbackStatement = Page.ClientScript. _
GetPostBackEventReference(options)
The generated script changes to the following:
WebForm_DoPostBackWithOptions(
new WebForm_PostBackOptions(ctl.name, "", false, "", _
"target.aspx", false, true));
As a result, when the user presses the Enter key on a PostTextBox control, the page posts back and the user is redirected to the specified page (target.aspx in the preceding example).
Conclusion
The TextBox control is not designed to handle the Enter key. Nonetheless, many users at the end of a form find it natural to press Enter instead of moving the mouse and clicking a submit button. In a page with a single textbox, pressing Enter when the textbox holds the input focus works just fine and the page posts back. However, it doesn t work for pages with more than one TextBox. That s why you need to have a specialized TextBox control to handle the Enter keystroke properly. In this article, I ve discussed a custom PostTextBox control that implements the IButtonControl interface (a new interface in ASP.NET 2.0) and supports validation and cross-page postbacks.
The sample code accompanying this article is available for download.
Dino Esposito is a Solid Quality Learning mentor and the author of Programming Microsoft ASP.NET 2.0 Core Reference and Programming Microsoft ASP.NET 2.0 Applications-Advanced Topics, both from Microsoft Press. Based in Italy, Dino is a frequent speaker at industry events worldwide. Join the blog at http://weblogs.asp.net/despos.
Begin Listing One Implementation of the IButtonControl interface
Public Event Click(ByVal sender As Object, ByVal e As _
EventArgs) Implements IButtonControl.Click
Public Event Command(ByVal sender As Object, ByVal e As _
CommandEventArgs) Implements IButtonControl.Command
Public Shadows Property CausesValidation() As Boolean _
Implements IButtonControl.CausesValidation
Get
Return MyBase.CausesValidation
End Get
Set(ByVal value As Boolean)
MyBase.CausesValidation = value
End Set
End Property
Public Property CommandArgument() As String _
Implements IButtonControl.CommandArgument
Get
Dim o As Object = ViewState("CommandArgument")
If o Is Nothing Then
Return String.Empty
End If
Return DirectCast(o, String)
End Get
Set(ByVal value As String)
ViewState("CommandArgument") = value
End Set
End Property
Public Property CommandName() As String _
Implements IButtonControl.CommandName
Get
Dim o As Object = ViewState("CommandName")
If o Is Nothing Then
Return String.Empty
End If
Return DirectCast(o, String)
End Get
Set(ByVal value As String)
ViewState("CommandName") = value
End Set
End Property
Public Property PostBackUrl() As String _
Implements IButtonControl.PostBackUrl
Get
Dim o As Object = ViewState("PostBackUrl")
If o Is Nothing Then
Return String.Empty
End If
Return DirectCast(o, String)
End Get
Set(ByVal value As String)
ViewState("PostBackUrl") = value
End Set
End Property
Public Shadows Property Text() As String _
Implements IButtonControl.Text
Get
Return MyBase.Text
End Get
Set(ByVal value As String)
MyBase.Text = value
End Set
End Property
Public Shadows Property ValidationGroup() As String _
Implements IButtonControl.ValidationGroup
Get
Return MyBase.ValidationGroup
End Get
Set(ByVal value As String)
MyBase.ValidationGroup = value
End Set
End Property
End Listing One