Chapter 32. Integrating JavaScript in Custom Controls

<feature><title>In this Chapter</title> <objective>

Using the ClientScriptManager Class

</objective>
<objective>

Building JavaScript Controls

</objective>
<objective>

Building AJAX Controls

</objective>
<objective>

Summary

</objective>
</feature>

Even though the ASP.NET Framework is a server-side programming framework, there is nothing to prevent you from taking advantage of JavaScript—a client-side programming language—in your custom controls. In fact, you can do many exciting things by integrating JavaScript into your custom controls.

If you want to create rich and interactive user interfaces then you have no choice but to use JavaScript. For example, by taking advantage of JavaScript, you can create controls that display floating windows, rich text boxes, and drag-and-drop interfaces. In other words, by taking advantage of JavaScript, you can create the same type of experience that users have come to expect from working with traditional desktop applications.

In this chapter, you learn how to integrate JavaScript into your custom controls. First, you learn how to take advantage of the methods and properties of the ClientScriptManager class. This class exposes the main application programming interface for working with client-side scripts. You also learn how to detect the features of different browsers by using the HttpBrowserCapabilities class.

Next, we get our hands dirty by building several controls that use client-side JavaScript. In the second part of this chapter, we build the following controls:

  • NewWindowLinkThis control renders a button that opens a new browser window. By setting properties of the control, you can configure the position and size of the new browser window.

  • WebWindowThis control renders a virtual browser window by rendering a floating <div> tag. The WebWindow control enables you to display multiple windows in a single page.

  • ClientTabsThis control enables you to divide the content displayed in a page into multiple tabs. Only the contents of a single tab are displayed at one time. When you switch tabs, the page is not posted back to the server.

In the final section of this chapter, we discuss my favorite topic in the universe: AJAX. By taking advantage of AJAX, a custom control can communicate with the web server without posting the page that contains the control back to the web server.

In the final section of this chapter, we build two AJAX controls:

  • ServerTimeButtonThis control renders a button. When you click the button, the current time is retrieved from the server and displayed in a browser alert dialog box.

  • ComboBoxThis control displays a drop-down list of matching records from a database as you type.

Note

One reason that many programmers avoid using JavaScript is the issue of browser compatibility. Client-side programming is a mess because different browsers implement JavaScript and the Browser Object Model in different ways. However, by taking advantage of feature detection, you can write JavaScript that works the same way across all modern browsers. All the controls discussed in this chapter are compatible with Internet Explorer 6, Firefox 1, and Opera 8.

Using the ClientScriptManager Class

The ClientScriptManager class contains the main application programming interface for working with JavaScript. You’ll make heavy use of this class whenever you add JavaScript to your custom controls.

The ClientScriptManager class supports the following methods for adding JavaScript to a page:

  • RegisterArrayDeclarationEnables you to add a JavaScript array to a page.

  • RegisterClientScriptBlockEnables you to add a JavaScript script after the page’s opening server-side <form> tag.

  • RegisterClientScriptIncludeEnables you to add a JavaScript include after the page’s opening server-side <form> tag.

  • RegisterClientScriptResourceEnables you to add JavaScript in a page that has been compiled into an assembly.

  • RegisterExpandoAttributeEnables you to add script for adding an expando attribute to an element of the page.

  • RegisterHiddenFieldEnables you to add a hidden form field after the page’s opening server-side <form> tag.

  • RegisterOnSubmitStatementEnables you to add JavaScript that executes immediately before a page is posted back to the server.

  • RegisterStartupScriptEnables you to add a JavaScript script before the page’s closing server-side <form> tag.

Notice that there are two methods for rendering a JavaScript script in the body of a page: RegisterClientScriptBlock() and RegisterStartupScript(). The only difference between these methods is the location where they render the JavaScript. The location of a JavaScript script in a page matters because you cannot refer to an HTML element in JavaScript unless the script is located after the element. If you use the RegisterStartupScript() method, then you know that all the HTML elements in the body of the server-side <form> tag have been created.

All the methods listed here were designed so that you can safely call them more than once. Because you might have multiple instances of the same control in the same page, you don’t want to add duplicate instances of the same script to a page. For example, if you call the RegisterClientScriptInclude() method more than once, then only one JavaScript include is added to the page.

You can detect whether or not a script has already been registered in a page by using one of the following methods:

  • IsClientScriptBlockRegisteredReturns true when a script has already been registered with the RegisterClientScriptBlock() method.

  • IsClientScriptIncludeRegisteredReturns true when a JavaScript include has already been registered with the RegisterClientScriptInclude() method.

  • IsOnSubmitStatementRegisteredReturns true when a script has already been registered with the RegisterOnSubmitStatement() method.

  • IsStartupScriptRegisteredReturns true when a script has already been registered with the RegisterStartupScript() method.

Detecting Browser Capabilities

After you have entered the messy universe of JavaScript, you must handle the frustrating incompatibilities between different web browsers. For example, you don’t want to call the showModalDialog() or addEventListener() method on a browser that doesn’t support it. You can detect browser capabilities either on the client side or the server side.

On the client side, you can perform feature detection in your JavaScript scripts to check whether particular methods are supported by a browser. For example, Internet Explorer and Firefox use different methods for adding an event handler. Internet Explorer uses the attachEvent() method and Firefox uses the (more standards-compliant) addEventListener() method.

The following script correctly adds a load event handler in the case of both browsers:

if (window.addEventListener)
   window.addEventListener('load', doSomething, false);
else
   window.attachEvent('onload', doSomething);

When you request a page that contains this script with Internet Explorer, calling window.addEventListener returns a value equivalent to false and the window.attachEvent() method is used. When you request a page that contains this script with Firefox or Opera, on the other hand, the window.addEventListener() method is called.

On the server side, you can use the properties of the HttpBrowserCapabilities class to detect the features of the browser being used to request a page. This class has a huge number of properties (too many to list here). However, here are some of the more useful properties that you can detect:

  • ActiveXControlsReturns true when a browser supports ActiveXControls.

  • AOLReturns true when a browser is an American Online browser.

  • BrowserReturns the type of browser (for example, IE, Firefox, Opera).

  • ClrVersionReturns the latest version of the .NET Framework installed on the browser.

  • CookiesReturns true when a browser supports cookies.

  • EcmaScriptVersionReturns the version of JavaScript supported by the browser.

  • MajorVersionReturns the major version of the browser as an Integer.

  • MinorVersionReturns the minor version of the browser as a couble.

  • MinorVersionStringReturns the minor version of the browser as a string.

  • MSDomVersionReturns the version of the Microsoft Document Object Model supported by the browser.

  • PlatformReturns the platform of the client (for example, WinXP).

  • SupportsCallbackReturns true when a browser supports AJAX.

  • SupportsCSSReturns true when a browser supports Cascading Style Sheets.

  • VersionReturns the full version of the browser.

  • W3CDomVersionReturns the W3C Document Object Model version supported by the browser (for example, 1.0).

The HttpBrowserCapabilities object is exposed through the Request object. You use Request.Browser to get a reference to the HttpBrowserCapabilities object. For example, you can use the following code to execute a subroutine only when the requesting browser is Internet Explorer version 5.0 or greater:

If Request.Browser.Browser = "IE" And Request.Browser.MajorVersion >= 5 Then
    doSomething()
End If

Behind the scenes, the HttpBrowserCapabilities object uses the User-Agent header sent by a browser to determine the browser’s capabilities. A database of browser capabilities is stored in a set of XML files located in the following folder:

WINDOWSMicrosoft.NETFramework[version]CONFIGBrowsers

The information reported back by these properties is only as accurate as the information stored in these XML files.

Building JavaScript Controls

In this section, you learn how to build three custom JavaScript controls. You start with a simple sample of a custom control that renders JavaScript. We build a NewWindowLink control that enables you to open a new browser window when you click a button.

Next, we build a WebWindow control that enables you to render a virtual browser window. This control enables you to simulate multiple windows in a single web form page.

Finally, we create a ClientTabs control. This control enables you to switch between different tabs of content on the client without a postback to the server.

Building a NewWindowLink Control

Being a good web developer is hard. To be a good web developer, you need to know HTML, Cascading Style Sheets, SQL, XML, and JavaScript. You also need to know about the different implementations of these technologies in the case of different browsers and different operating systems.

One of the main jobs of a framework, such as the ASP.NET Framework, is to shield you from all the underlying technologies on which the framework is built. If you integrate JavaScript into a custom control, then you can learn the JavaScript for five minutes and then never need to worry about it again.

For example, in this section, we build a NewWindowLink control that opens a new browser window (see Figure 32.1). The JavaScript required to open a new window is quite simple. However, the advantage of creating a control that opens a new window is that you don’t need to remember the JavaScript in the future.

Opening a new browser window with the NewWindowLink control.

Figure 32.1. Opening a new browser window with the NewWindowLink control.

The code for the NewWindowLink control is contained in Listing 32.1.

Example 32.1. NewWindowLink.vb

Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace myControls

    Public Class NewWindowLink
        Inherits WebControl
        Private _text As String = "Click Here!"
        Private _navigateUrl As String
        Private _target As String = "_blank"
        Private _windowWidth As Integer = 400
        Private _windowHeight As Integer = 300
        Private _windowLeft As Integer = 100
        Private _windowTop As Integer = 100
        Private _fullScreen As Boolean = False
        Private _resizable As Boolean = True

        Public Property Text() As String
            Get
                Return _text
            End Get
            Set(ByVal Value As String)
                _text = value
            End Set
        End Property

        Public Property NavigateUrl() As String
            Get
                Return _navigateUrl
            End Get
            Set(ByVal Value As String)
                _navigateUrl = value
            End Set
        End Property

        Public Property Target() As String
            Get
                Return _target
            End Get
            Set(ByVal Value As String)
                _target = value
            End Set
        End Property

        Public Property WindowWidth() As Integer
            Get
                Return _windowWidth
            End Get
            Set(ByVal Value As Integer)
                _windowWidth = value
            End Set
        End Property

        Public Property WindowHeight() As Integer
            Get
                Return _windowHeight
            End Get
            Set(ByVal Value As Integer)
                _windowHeight = value
            End Set
        End Property

        Public Property WindowLeft() As Integer
            Get
                Return _windowLeft
            End Get
            Set(ByVal Value As Integer)
                _windowLeft = value
            End Set
        End Property

        Public Property WindowTop() As Integer
            Get
                Return _windowTop
            End Get
            Set(ByVal Value As Integer)
                _windowTop = value
            End Set
        End Property

        Public Property FullScreen() As Boolean
            Get
                Return _fullScreen
            End Get
            Set(ByVal Value As Boolean)
                _fullScreen = value
            End Set
        End Property

        Public Property Resizable() As Boolean
            Get
                Return _resizable
            End Get
            Set(ByVal Value As Boolean)
                _resizable = value
            End Set
        End Property

        Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)
            Dim fullscreenValue As String = "no"
            If (_fullScreen) Then
                fullscreenValue = "yes"
            End If
            Dim resizableValue As String = "no"
            If (_resizable) Then
                resizableValue = "yes"
            End If
            Dim features As String = "width={0},height={1},left={2},top={3},fullscreen={4}, resizable={5},status=no,toolbar=no,menubar=no,location=no"
            Dim featuresValue As String = String.Format(features, _windowWidth, _windowHeight, _windowLeft, _ windowTop, fullscreenValue, resizableValue)
            Dim script As String = String.Format("window.open('{0}','{1}','{2}'),return false;", Page.ResolveUrl(_navigateUrl), _target, featuresValue)
            writer.AddAttribute(HtmlTextWriterAttribute.Onclick, script)
            writer.AddAttribute(HtmlTextWriterAttribute.Href, Page.ResolveUrl(_navigateUrl))
            MyBase.AddAttributesToRender(writer)
        End Sub

        Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
            writer.Write(_text)
        End Sub

        Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag
            Get
                Return HtmlTextWriterTag.A
            End Get
        End Property

    End Class
End Namespace

The majority of the code in Listing 32.1 is devoted to declaring a set of properties. The NewWindowLink control includes properties for the new window’s position and size. It also includes properties you can set to open a window that is resizable or full screen.

The JavaScript for opening the window is contained in the AddAttributesToRender() method. This method adds a client-side OnClick handler to the link rendered by the control. When you click the link, the window.open() method is called on the client.

The page in Listing 32.2 illustrates how you can use the NewWindowLink control in an ASP.NET page. The page contains two instances of the NewWindowLink control. The first instance opens a normal window. The control opens the page specified by its NavigateUrl property in a new window.

The second instance of the NewWindowLink control opens a full-screen window. A full-screen window is supported only with Internet Explorer. (You get a normal new window when the control is used with other browsers such as Firefox.) After you open a full-screen window, you can close it by selecting Alt+F4.

Example 32.2. ShowNewWindowLink.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 id="Head1" runat="server">
    <title>Show NewWindowLink</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <custom:NewWindowLink
        id="NewWindowLink1"
        Text="Open Window"
        NavigateUrl="~/Default.aspx"
        Runat="server" />

    <br /><br />

    <custom:NewWindowLink
        id="NewWindowLink2"
        Text="Open Full Screen Window"
        NavigateUrl="~/Default.aspx"
        FullScreen="true"
        Runat="server" />

    </div>
    </form>
</body>
</html>

Building a WebWindow Control

There are several disadvantages that result from using separate browser windows in a web application. First, communicating information between multiple browser windows is difficult. When you update one window, you must reload the other windows to reflect the new information. Furthermore, new browser windows can be blocked by pop-up blockers.

A better alternative is to create virtual windows by creating floating <div> tags. Unlike true browser windows, you don’t run into problems communicating information between virtual windows. Furthermore, a virtual window will never be blocked by a pop-up blocker.

By taking advantage of a little JavaScript, you can even drag and drop a virtual window. In other words, you can position a virtual window at different locations on the screen.

The code for the virtual window control, the WebWindow control, is contained in Listing 32.3.

Example 32.3. WebWindow.vb

Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.ComponentModel

Namespace myControls

    <ParseChildren(False)> _
    Public Class WebWindow
        Inherits WebControl
        Implements IPostBackEventHandler

        Private _windowTitleText As String = "Untitled"

        Public Event Closed As EventHandler

        Public Property WindowTitleText() As String
            Get
                Return _windowTitleText
            End Get
            Set(ByVal Value As String)
                _windowTitleText = value
            End Set
        End Property

        Protected Overrides Sub OnPreRender(ByVal e As EventArgs)
            If Not Page.ClientScript.IsClientScriptIncludeRegistered("WebWindow") Then
                Page.ClientScript.RegisterClientScriptInclude("WebWindow", Page.ResolveClientUrl("~/ClientScripts/WebWindow.js"))
            End If

            Dim startupScript As String = String.Format("WebWindow.init('{0}'),", Me.ClientID)
            Page.ClientScript.RegisterStartupScript(Me.GetType(), Me.ClientID, startupScript, True)
        End Sub
        Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
            RenderTitleBar(writer)
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "webWindowBody")
            writer.RenderBeginTag(HtmlTextWriterTag.Div)
            Me.RenderChildren(writer)
            writer.RenderEndTag()
        End Sub

        Private Sub RenderTitleBar(ByVal writer As HtmlTextWriter)
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "webWindowTitleBar")
            writer.AddAttribute("onmousedown", "WebWindow.mouseDown(event)")
            writer.AddStyleAttribute(HtmlTextWriterStyle.TextAlign, "right")
            writer.RenderBeginTag(HtmlTextWriterTag.Div)

            writer.AddAttribute(HtmlTextWriterAttribute.Class, "webWindowTitleText")
            writer.RenderBeginTag(HtmlTextWriterTag.Span)
            writer.Write(_windowTitleText)
            writer.RenderEndTag()

            RenderCloseButton(writer)

            writer.RenderEndTag()
        End Sub

        Private Sub RenderCloseButton(ByVal writer As HtmlTextWriter)
            Dim eventRef As String = Page.ClientScript.GetPostBackEventReference(Me, String.Empty)
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "webWindowClose")
            writer.AddAttribute(HtmlTextWriterAttribute.Onclick, eventRef)
            writer.RenderBeginTag(HtmlTextWriterTag.Span)
            writer.Write("X")
            writer.RenderEndTag()
        End Sub

        Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)
            writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute")
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "webWindow")
            MyBase.AddAttributesToRender(writer)
        End Sub

        Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag
            Get
                Return HtmlTextWriterTag.Div
            End Get
        End Property

        Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements IPostBackEventHandler.RaisePostBackEvent
            RaiseEvent Closed(Me, EventArgs.Empty)
        End Sub
    End Class
End Namespace

The RenderContents() method in Listing 32.3 is responsible for rendering the virtual window. This method renders a title bar that contains a close button. It also renders a <div> tag that contains the window’s contents.

All the magic happens in an external JavaScript file. A JavaScript include is rendered by the WebWindow control in its OnPreRender() method. The control assumes that a JavaScript file named WebWindow.js is located in a subfolder named ClientScripts.

The contents of the WebWindow.js file are contained in Listing 32.4.

Example 32.4. ClientScriptsWebWindow.js

var WebWindow = new Object();

WebWindow.init = function(winId)
    {
      eval("place=" + this.getCookie(winId + "_place"));
      place = place || {left:20, top:20};

      var win = document.getElementById(winId);

      win.style.left = place.left;
      win.style.top = place.top;
    }

WebWindow.mouseDown = function(e)
    {
        var e = e || window.event;
        var src = e.target || e.srcElement;
        var win = src.offsetParent;

        var startWinX = win.offsetLeft;
        var startWinY = win.offsetTop;
        var startMouseX = e.clientX;
        var startMouseY = e.clientY;

        var move = function(e)
            {
                var e = e || window.event;
                win.style.left = (startWinX - (startMouseX - e.clientX)) + 'px';
                win.style.top = (startWinY - (startMouseY - e.clientY)) + 'px';

                if (document.all)
                {
                    e.cancelBubble = true;
                    e.returnValue = false;
                }
                if (e.preventDefault)
                {
                    e.preventDefault();
                }
            }

        var up = function(e)
            {
                document.onmousemove = null;
                document.onmouseup = null;
                WebWindow.setCookie(win.id + "_place", "{left:'" + win.style.left
+ "', top:'" + win.style.top + "'}");
            }

        document.onmousemove = move;
        document.onmouseup = up;
    }

WebWindow.setCookie = function(name, value)
{
    var expires = new Date();
    expires.setTime( expires.getTime() + 365 * 24 * 60 * 60 * 1000 );
    document.cookie = name + "=" + escape(value) +
";expires=" + expires.toGMTString();
        }


WebWindow.getCookie = function(name)
{
      var aCookie = document.cookie.split("; ");
      for (var i=0; i < aCookie.length; i++)
      {
        var aCrumb = aCookie[i].split("=");
        if (name == aCrumb[0])
        return unescape(aCrumb[1]);
      }
      return null;
}

The JavaScript file in Listing 32.4 contains the methods that enable you to drag the WebWindow around the page. When you click your mouse on the WebWindow’s title bar, the WebWindow.mouseDown() method executes and records your current mouse position. As you move your mouse, the move() method executes and updates the WebWindow's position on the screen by modifying the left and top style properties of the WebWindow’s containing div element.

Whenever you move a WebWindow, its new position is recorded in a browser cookie. The next time that you open the page, the WebWindow appears in the same location.

Warning

Be careful about where you declare the WebWindow control in a page. Do not declare the control inside of another <div> tag or you might encounter difficulties when you attempt to drag the control outside of its containing <div> tag. I’ve tested the WebWindow control with Internet Explorer 6.0, Firefox 1.0, and Opera 8.0.

The page in Listing 32.5 illustrates how you can use the WebWindow control in an ASP.NET page. When the page first opens, the WebWindow is not visible by default. When you click the Open Window link, the WebWindow is displayed (see Figure 32.2). Finally, when you click the WebWindow’s Close button, the WebWindow1_Closed() event handler executes and the WebWindow is hidden.

Example 32.5. ShowWebWindow.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 lnkOpenWindow_Click(sender As object, e As EventArgs)
        WebWindow1.Visible = true
    End Sub

    protected sub WebWindow1_Closed(sender As object, e As EventArgs)
        WebWindow1.Visible = false
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <style type="text/css">
        .webWindow
        {
            width:400px;
            height:400px;
            border:Outset;
            background-color:white;
        }
        .webWindowBody
        {
            padding:10px;
        }
        .webWindowTitleBar
        {
            font:14px Verdana,Sans-Serif;
            padding-left:10px;
            background-color:Blue;
            color:white;
            cursor:move;
        }
        .webWindowTitleText
        {
            float:left;
        }
        .webWindowClose
        {
            background-color:Red;
            cursor:pointer;
        }
    </style>
    <title>Show WebWindow</title>
</head>
<body>
    <form id="form1" runat="server">

    <div>
    <asp:LinkButton
        id="lnkOpenWindow"
        Text="Open Window"
        OnClick="lnkOpenWindow_Click"
        Runat="server" />
    </div>

    <custom:WebWindow
        id="WebWindow1"
        WindowTitleText="The WebWindow Title"
        Visible="false"
        OnClosed="WebWindow1_Closed"
        Runat="server">
        Here is some content
    </custom:WebWindow>

    </form>
</body>
</html>
Displaying a virtual window with the WebWindow control.

Figure 32.2. Displaying a virtual window with the WebWindow control.

Building a ClientTabs Control

In Chapter 4, “Using the Rich Controls,” you learned how to use the Menu control with the MultiView control to display tabbed content in a page. The drawback of using these controls is that they require you to post the page containing the controls back to the server each and every time you click a tab.

In this section, you learn how to create a client-side tab control named the ClientTabs control (see Figure 32.3). This control renders the contents of all the tabs to the browser. However, it hides the contents of all the tabs except for the selected tab. When you select a new tab, JavaScript is executed on the browser to hide the contents of all the tabs except the newly selected tab.

Displaying tabs with the ClientTabs control.

Figure 32.3. Displaying tabs with the ClientTabs control.

Unfortunately, the entire code for the ClientTabs control is too long to include in the body of this book. However, the complete source code (in both Visual Basic .NET and C#) is included on the CD that accompanies this book. Listing 32.6 contains a partial listing of the ClientTabs control.

Example 32.6. ClientTabs.vb (Partial Listing)

Namespace myControls
    <ParseChildren(False)> _
    Public Class ClientTabs
        Inherits WebControl
        Implements IPostBackDataHandler

        ''' <summary>
        ''' Render tabs and tab content
        ''' </summary>
        Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
            RenderTabs(writer)
            RenderTabContent(writer)
        End Sub

        ''' <summary>
        ''' Render the tab strip
        ''' </summary>
        Private Sub RenderTabs(ByVal writer As HtmlTextWriter)
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "tabs")
            writer.RenderBeginTag(HtmlTextWriterTag.Table)
            writer.RenderBeginTag(HtmlTextWriterTag.Tbody)
            writer.AddAttribute(HtmlTextWriterAttribute.Id, TabContainerID)
            writer.RenderBeginTag(HtmlTextWriterTag.Tr)

            Dim index As Integer
            For index = 0 To Controls.Count - 1 Step index + 1
                Dim currentTab As Tab = CType(Controls(index), Tab)
                Dim script As String = String.Format("ClientTabs.selectTab('{0}','{1}')", Me.ClientID, currentTab.ClientID)
                writer.AddAttribute(HtmlTextWriterAttribute.Onclick, script)
                If index = SelectedIndex Then
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "tab selectedTab")
                Else
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "tab unselectedTab")
                End If
                writer.AddAttribute(HtmlTextWriterAttribute.Id, currentTab.ClientID + "_tab")
                writer.RenderBeginTag(HtmlTextWriterTag.Td)
                writer.Write(currentTab.Text)
                writer.RenderEndTag()
            Next

            writer.RenderEndTag()
            writer.RenderEndTag()
            writer.RenderEndTag()
        End Sub

        ''' <summary>
        ''' Render the tab contents
        ''' </summary>
        Private Sub RenderTabContent(ByVal writer As HtmlTextWriter)
            writer.AddAttribute(HtmlTextWriterAttribute.Id, TabContentContainerID)
            writer.AddAttribute(HtmlTextWriterAttribute.Class, "tabContent")
            writer.RenderBeginTag(HtmlTextWriterTag.Div)

            Dim index As Integer
            For index = 0 To Controls.Count - 1 Step index + 1
                Dim currentTab As Tab = CType(Controls(index), Tab)
                If index <> SelectedIndex Then
                    currentTab.Style.Add("display", "none")
                End If

                currentTab.RenderControl(writer)
            Next
            writer.RenderEndTag()
        End Sub
    End Class
End Namespace

Listing 32.6 contains the code for rendering the tabs. When the ClientTab control renders its contents, it iterates through its child controls in two passes. First, it renders the tab links. Next, it renders the body of each tab. Any tab that is not selected is hidden by the CSS display:none property.

One special feature of the ClientTabs control needs to be explained. Imagine that each of the tabs contains a separate form with a separate button. When you click the button and submit the form to the server, you want the same tab to be displayed again when the page is reloaded. In other words, to function properly, the ClientTabs control needs to retain state across postbacks.

The ClientTabs control registers a hidden form field in its OnPreRender() method. When you select a new tab, the ID of the selected tab is assigned to the hidden form field. When the page is posted back to the server, the value of the hidden form field is used to determine the currently selected tab. That way, when the ClientTab control renders its tabs again, the tab selected on the client remains selected when the control is rendered on the server.

The ClientTabs control uses a Cascading Style Sheet file to format the appearance of the tabs. This Cascading Style Sheet file is added to the page containing the ClientTabs control by the ClientTabs control’s OnPreRender() method. The contents of the Cascading Style Sheet file are contained in Listing 32.7.

Example 32.7. ClientScriptsClientTabs.css

.tab
{
    padding:2px 25px;
    border:solid 1px black;
    cursor:pointer;
}

.unselectedTab
{
    background-color:#eeeeee;
}

.selectedTab
{
    background-color:White;
    border-bottom:solid 1px white;
}

.tabs
{
    position:relative;
    top:3px;
    left:15px;
}


.tabContent
{
    border:Solid 1px black;
    background-color:White;
    padding:10px;
}

Finally, the ClientTabs control executes a client-side JavaScript function named ClientTabs.selectTab() when a user selects a new tab. The JavaScript library is added to the page in the ClientTabs control’s OnPreRender() method. The ClientTabs control assumes that there is a JavaScript file named ClientTabs.js that is located in the ClientScripts folder. The contents of this JavaScript file are contained in Listing 32.8.

Example 32.8. ClientScriptsClientTabs.js

var ClientTabs = new Object();

ClientTabs.selectTab = function(controlId, tabId)
    {
        // Get previous value
        var hiddenField = document.getElementById(controlId + '_hidden'),
        var prevTabId = hiddenField.value;

        // Hide previous tab
        document.getElementById(prevTabId + '_tab').className =
'tab unselectedTab';
        document.getElementById(prevTabId).style.display = 'none';

        // Show new tab
        document.getElementById(tabId).style.display = 'block';
        document.getElementById(tabId + '_tab').className = 'tab selectedTab';

        // Update hidden value
        hiddenField.value = tabId;
    }

The page in Listing 32.9 illustrates how you can use the ClientTabs control within an ASP.NET page. The page in Listing 32.9 contains a ClientTabs control that contains three Tab controls. The final Tab control includes a simple form with a Button control. Notice that after you submit the page to the server, the correct tab is selected when the tabs are rendered back to the client.

Example 32.9. ShowClientTabs.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 id="Head1" runat="server">
    <title>Show ClientTabs</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <custom:ClientTabs
        id="ClientTabs1"
        Runat="server">
        <custom:Tab ID="Tab1" Text="First Tab" runat="server">
            Contents of the first tab
        </custom:Tab>
        <custom:Tab ID="Tab2" Text="Second Tab" runat="server">
            Contents of the second tab
        </custom:Tab>
        <custom:Tab ID="Tab3" Text="Third Tab" runat="server">
            Contents of the third tab
            <br /><br />
            <asp:Label
                id="lblUserName"
                Text="User Name:"
                AssociatedControlID="txtUserName"
                Runat="server" />
            <asp:TextBox
                id="txtUserName"
                Runat="server" />
            <asp:Button
                id="btnSubmit"
                Text="Submit"
                Runat="server" />
        </custom:Tab>
    </custom:ClientTabs>

    </div>
    </form>
</body>
</html>

Building AJAX Controls

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:

  • controlThe control that initiates the AJAX call.

  • argumentThe argument that is sent to the web server in the AJAX call.

  • clientCallbackThe name of the JavaScript function that executes after a result is returned from the web server.

  • contextThe argument that is passed back to the clientCallback() and clientErrorCallback() methods after the AJAX call completes.

  • clientErrorCallbackThe name of the JavaScript function that executes when an error on the server results from an AJAX call.

  • useAsyncWhen 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:

  • RaiseCallbackEventThis method is called first on the server when an AJAX call is performed.

  • GetCallbackResultThis method returns the result of the AJAX call to the client.

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.

Displaying the server time with the ServerTimeButton control.

Figure 32.4. Displaying the server time with the ServerTimeButton control.

Example 32.10. ServerTimeButton.vb

Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls

Namespace myControls
    Public Class ServerTimeButton
        Inherits WebControl
        Implements ICallbackEventHandler

        ''' <summary>
        ''' Add the onclick attribute that initiates the AJAX call
        ''' </summary>
        Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)
            Dim eRef As String = Page.ClientScript.GetCallbackEventReference( _
                Me, _
                Nothing, _
                "showResult", _
                Nothing, _
                "showError", _
                False)

            writer.AddAttribute(HtmlTextWriterAttribute.Onclick, eRef)
            MyBase.AddAttributesToRender(writer)
        End Sub

        ''' <summary>
        ''' Add the Javascript file that process the AJAX call results
        ''' to the page
        ''' </summary>
        Protected Overrides Sub OnPreRender(ByVal e As EventArgs)
            Page.ClientScript.RegisterClientScriptInclude("serverTimeButton", Page.ResolveClientUrl("~/ClientScripts/ServerTimeButton.js"))
        End Sub

        Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)
            writer.Write("Show Server Time")
        End Sub

        Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag
            Get
                Return HtmlTextWriterTag.Button
            End Get
        End Property

        ''' <summary>
        ''' Called on server by AJAX
        ''' </summary>
        ''' <param name="eventArgument"></param>
        Public Sub RaiseCallbackEvent(ByVal eventArgument As String) Implements ICallbackEventHandler.RaiseCallbackEvent
        End Sub

        ''' <summary>
        ''' Returns result back to AJAX call
        ''' </summary>
        ''' <returns></returns>
        Public Function GetCallbackResult() As String Implements ICallbackEventHandler.GetCallbackResult
            'throw new Exception("Server Exception");
            Return DateTime.Now.ToString()
        End Function
    End Class
End Namespace

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.

Example 32.11. ClientScriptsServerTimeButton.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.

Example 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 id="Head1" runat="server">
    <title>Show Server Time</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    Initial Time: <%= DateTime.Now.ToString() %>

    <br /><br />

    <custom:ServerTimeButton
        id="lnkServerTime"
        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).

Using Google Suggest.

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).

Displaying matching moves with the ComboBox control.

Figure 32.6. Displaying matching moves with the ComboBox control.

The ComboBox control is contained in Listing 32.13.

Example 32.13. ComboBox.vb

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.

Example 32.14. ClientScriptsComboBox.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.

Example 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 id="Head1" runat="server">
    <title>Show ComboBox</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <custom:ComboBox
        id="ComboBox1"
        ConnectionString='<%$ ConnectionStrings:Movies %>'
        DataKeyName="Title"
        SelectCommand="SELECT Title FROM Movies
            WHERE Title LIKE @Title+'%'
            ORDER BY Title"
        Style="float:left"
        Runat="Server" />
    <asp:Button
        id="btnSubmit"
        Text="Submit"
        Runat="server" OnClick="btnSubmit_Click" />
    <hr />

    <asp:Label
        id="lblResult"
        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.

Summary

In this chapter, you learned how to build custom controls that take advantage of client-side JavaScript. In the first section, you learned how to use the ClientScriptManager class to add JavaScript scripts to a page. You also learned how to detect browser capabilities with the HttpBrowserCapabilities object.

Next, we built three JavaScript controls. You learned how to create a NewWindowLink control that renders a button that opens a new browser window. We also created a virtual window control named the WebWindow control. Finally, we created a ClientTabs control that renders a client-side tab strip.

The final section of this chapter explored the exciting topic of AJAX. You learned how to initiate an AJAX call to the server and implement the ICallbackEventHandler interface. We also created a ComboBox that uses AJAX to retrieve matching database records from the web server with each key press.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset