AJAX (Asynchronous JavaScript and XML) is the future of the web. By taking advantage of AJAX, you can avoid performing a postback each and every time you perform an action in an ASP.NET page. A control that uses AJAX can communicate directly with a web server by performing a "sneaky postback." Three of the standard ASP.NET controls use AJAX: the TReeView, GridView, and DetailsView controls. When you expand a node in a TReeView, the child nodes of the expanded node can be retrieved from the web server without a postback. When you sort or page rows in a GridView control, the rows can be retrieved from the server without a postback. Finally, when you page through records in a DetailsView control, you do not need to post the page back to the server. Note Microsoft has developed a new framework (code named Atlas) that enables you to build rich AJAX applications on top of the ASP.NET Framework. You can learn more about Atlas by visiting http://atlas.asp.net. In this section, you learn how to take advantage of AJAX when building custom controls. We'll start simple. First, we create a ServerTimeButton control that retrieves the current time from the server and displays it in the browser without requiring a postback. Next, we'll create a more practical control. We re-create Google Suggest by creating an AJAX-enabled ComboBox control. Implementing AJAX To implement AJAX in a custom control, you must perform the following steps: 1. | Render the JavaScript that initiates the AJAX call.
| 2. | Create the methods on the server that reply to the AJAX call.
| 3. | Create the JavaScript on the browser that displays the result from the server.
| You initiate an AJAX call from the browser in order to execute methods on the server. The server returns a result that can be used on the client. You create the JavaScript that initiates the AJAX call by calling the Page.ClientScripts.GetCallbackEventReference() method. This method returns a string that represents a JavaScript function call that looks like this: WebForm_DoCallback('myControl',null,showResult,null,showError,false) The GetCallbackEventReference() method is overloaded. Here are the parameters for one of the overloaded versions of this method: control The control that initiates the AJAX call. argument The argument that is sent to the web server in the AJAX call. clientCallback The name of the JavaScript function that executes after a result is returned from the web server. context The argument that is passed back to the clientCallback() and clientErrorCallback() methods after the AJAX call completes. clientErrorCallback The name of the JavaScript function that executes when an error on the server results from an AJAX call. useAsync When true, the AJAX call is performed asynchronously. Next, you need to implement the methods on the server that respond to the AJAX call. To do this, you need to implement the ICallbackEventHandler interface. Note AJAX, in Microsoft terminology, is called client callbacks. The name AJAX was invented in a Blog post by Jesse James Garrett. The ICallbackEventHandler interface has two methods that you must implement: Finally, you must implement a JavaScript function on the client that is called when the results are returned from the server. Building a ServerTimeButton Control We start by creating a really simple control that uses AJAX. The ServerTimeButton control retrieves the current time from the server and displays it in the web browser (see Figure 32.4). The ServerTimeButton control is contained in Listing 32.10. Figure 32.4. Displaying the server time with the ServerTimeButton control. Listing 32.10. ServerTimeButton.vb The ServerTimeButton control renders an HTML button. Clicking the button initiates the AJAX call to the server. The script for initiating the AJAX call is generated in the AddAttributesToRender() method by a call to the Page.ClientScript.GetCallbackEventReference() method. Notice that the ServerTimeButton control implements the ICallbackEventHandler interface. This interface includes two methods named RaiseCallbackEvent and GetCallbackResult(). In Listing 32.10, the RaiseCallbackEvent() does nothing. The GetCallbackResult() method returns the current time as a string. Finally, the ServerTimeButton control uses a JavaScript file named ServerTimeButton.js. This file is registered in the control's OnPreRender() method. The contents of the ServerTimeButton.js file are contained in Listing 32.11. Listing 32.11. ClientScripts\ServerTimeButton.js function showResult(result, context) { alert( result ); } function showError(error, context) { alert( error ); } | The JavaScript file in Listing 32.11 includes two functions. The first function, named showResult(), displays the result of the AJAX call. This method simply displays the server time in a JavaScript alert box. The showError() function displays any error message returned by the server as a result of the AJAX call. The page in Listing 32.12 uses the ServerTimeButton control. Listing 32.12. ShowServerTimeButton.aspx <%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Show Server Time</title> </head> <body> <form runat="server"> <div> Initial Time: <%= DateTime.Now.ToString() %> <br /><br /> <custom:ServerTimeButton Runat="Server" /> </div> </form> </body> </html> | If you open the page in Listing 32.12 and click the button, the time is retrieved from the server and displayed in the browser. At no time is the page posted back to the server. The server time is retrieved through an AJAX call. Building an AJAX Combobox Control The application that caused all the excitement over AJAX was Google Suggest (http://www.google.com/webhp?complete=1). Google Suggest is an enhanced version of Google Search. As you type a search phrase, Google Suggest automatically displays a list of matching entries in a drop-down list (see Figure 32.5). Figure 32.5. Using Google Suggest. The amazing thing about Google Suggest is that every time you enter a new letter into the search box, an AJAX call is performed to retrieve a list of matching results from the server. The fact that the AJAX calls can be performed in real time blew everyone's mind. In this section, we create a ComboBox control that mimics the functionality of Google Suggest. Each time you enter a new letter into the combo box, a new AJAX call is performed against the web server to retrieve matching entries (see Figure 32.6). Figure 32.6. Displaying matching moves with the ComboBox control. The ComboBox control is contained in Listing 32.13. Listing 32.13. ComboBox.vb [View full width] Imports System Imports System.Collections.Generic Imports System.Data Imports System.Configuration Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls Public Class ComboBox Inherits CompositeControl Implements ICallbackEventHandler Private _comboTextBox As TextBox Private _dataKeyName As String = String.Empty Private _selectCommand As String = String.Empty Private _connectionString As String = String.Empty Private _clientArgument As String ''' <summary> ''' Name of the database field used for the lookup ''' </summary> Public Property DataKeyName() As String Get Return _dataKeyName End Get Set(ByVal Value As String) _dataKeyName = value End Set End Property ''' <summary> ''' SQL Select command issued against database ''' </summary> Public Property SelectCommand() As String Get Return _selectCommand End Get Set(ByVal Value As String) _selectCommand = value End Set End Property ''' <summary> ''' Connection String for database ''' </summary> Public Property ConnectionString() As String Get Return _connectionString End Get Set(ByVal Value As String) _connectionString = value End Set End Property Private ReadOnly Property ComboSelectId() As String Get Return Me.ClientID + "_select" End Get End Property Public Property Text() As String Get EnsureChildControls() Return _comboTextBox.Text End Get Set(ByVal Value As String) EnsureChildControls() _comboTextBox.Text = value End Set End Property Public Property Columns() As Integer Get EnsureChildControls() Return _comboTextBox.Columns End Get Set(ByVal Value As Integer) EnsureChildControls() _comboTextBox.Columns = value End Set End Property Protected Overrides Sub OnPreRender(ByVal e As EventArgs) ' Make sure all the properties are set If _dataKeyName = String.Empty Then Throw New Exception("DataKeyName cannot be empty") End If If _connectionString = String.Empty Then Throw New Exception("ConnectionString cannot be empty") End If If _selectCommand = String.Empty Then Throw New Exception("SelectCommand cannot be empty") End If ' Register Include File If Not Page.ClientScript.IsClientScriptIncludeRegistered("ComboBox") Then Page.ClientScript.RegisterClientScriptInclude("ComboBox", Page.ResolveUrl ("~/ClientScripts/ComboBox.js")) End If End Sub Protected Overrides Sub CreateChildControls() ' Create the TextBox _comboTextBox = New TextBox() _comboTextBox.Attributes("autocomplete") = "off" Controls.Add(_comboTextBox) End Sub Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) ' Define the callback Dim callBackRef As String = Page.ClientScript.GetCallbackEventReference( _ Me, _ "this.value", _ "comboBox_ClientCallback", _ String.Format("'{0}'", ComboSelectId), _ "comboBox_ErrorCallback", _ True) ' Render the text box _comboTextBox.Attributes.Add("onkeyup", callBackRef) _comboTextBox.Attributes.Add("onblur", "comboBox_Blur(this)") _comboTextBox.RenderControl(writer) writer.WriteBreak() ' Render the drop-down writer.AddAttribute(HtmlTextWriterAttribute.Id, ComboSelectId) writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none") writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute") writer.RenderBeginTag(HtmlTextWriterTag.Select) writer.RenderEndTag() End Sub Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag Get Return HtmlTextWriterTag.Div End Get End Property Public Sub RaiseCallbackEvent(ByVal clientArgument As String) Implements ICallbackEventHandler.RaiseCallbackEvent _clientArgument = clientArgument End Sub Public Function GetCallbackResult() As String Implements ICallbackEventHandler .GetCallbackResult ' If no text, then return nothing If _clientArgument.Trim() = String.Empty Then Return "[]" End If ' Otherwise, get the matching rows Dim src As SqlDataSource = New SqlDataSource(_connectionString, _selectCommand) src.SelectParameters.Add(_dataKeyName, _clientArgument) Dim dvw As DataView Try dvw = CType(src.Select(DataSourceSelectArguments.Empty), DataView) Catch Return "[]" End Try ' Return matching rows in a JavaScript array Dim rows As New List(Of String)() Dim row As DataRowView For Each row In dvw rows.Add(String.Format("'{0}'", row(0).ToString().Replace("'", "\'"))) Next Return "[" + String.Join(",", rows.ToArray()) + "]" End Function End Class End Namespace | The control in Listing 32.13 renders a text box and a list box. As you type letters into the text box, a list of matching database records is displayed in the list box. The ComboBox uses the JavaScript functions contained in Listing 32.14. Listing 32.14. ClientScripts\ComboBox.js // Display rows from database in the SELECT tag function comboBox_ClientCallback(result, context) { // convert rows into an array var rows; eval( 'rows=' + result ); // Get the Select element var comboSelect = document.getElementById( context ); // Add the options comboSelect.options.length = 0; for (var i=0;i<rows.length;i++) { var newOption = document.createElement("OPTION"); newOption.text= rows[i]; newOption.value= rows[i]; if (document.all) comboSelect.add(newOption); else comboSelect.add(newOption, null); } // If results, show the SELECT, otherwise hide it if (comboSelect.options.length > 0) { comboSelect.size = comboSelect.options.length + 1; comboSelect.selectedIndex = 0; comboSelect.style.display='block'; } else comboSelect.style.display='none'; } // When leaving comboBox, get selected value from SELECT function comboBox_Blur(src) { var container = src.parentNode; var comboSelect = container.getElementsByTagName('select')[0]; if ( comboSelect.style.display != 'none' && comboSelect.selectedIndex != -1) src.value = comboSelect.value; comboSelect.style.display = 'none'; } // If server error, just show it function comboBox_ErrorCallback(result) { alert( result ); } | The JavaScript library in Listing 32.14 contains three functions. The first function, comboBox_ClientCallback(), displays the results of a database lookup after each callback. This function updates the list of matching entries displayed by the list box. The comboBox_Blur() function updates the TextBox with the item in the ListBox that matches the text entered into the TextBox. Finally, the comboBox_ErrorCallback() method displays any errors returned from the server. The page in Listing 32.15 illustrates how you can use the ComboBox control. When you enter text into the combo box, a list of matching movie titles is displayed. Listing 32.15. ShowComboBox.aspx <%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs) lblResult.Text = ComboBox1.Text End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Show ComboBox</title> </head> <body> <form runat="server"> <div> <custom:ComboBox ConnectionString='<%$ ConnectionStrings:Movies %>' DataKeyName="Title" SelectCommand="SELECT Title FROM Movies WHERE Title LIKE @Title+'%' ORDER BY Title" Style="float:left" Runat="Server" /> <asp:Button Text="Submit" Runat="server" OnClick="btnSubmit_Click" /> <hr /> <asp:Label Runat="server" /> </div> </form> </body> </html> | In Listing 32.15, the ConnectionString, DataKeyName, and SelectCommand properties are set. The ConnectionString property represents a connection to a database. The DataKeyName property represents a primary key column from a database table. Finally, the SelectCommand uses a SQL SELECT command to retrieve a list of matching records. Notice that the SELECT command uses a LIKE operator to retrieve all records that begin with the text entered into the combo box. |