堕落的卖猪贩-做人就象做诗,一旦上了境界,就下不来了。

我的Ber客我作主,今天你Ber没有。

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

本页内容
 简介
 创建基类作为添加客户端脚本的基础
 从代码隐藏类添加客户端脚本
 根据对用户操作的响应执行客户端代码
 实现常用客户端功能
 小结
 相关书籍

 

简介当使用动态的、基于Web的脚本技术时,与传统ASP或PHP类似,开发人员必须对客户端和服务器间的逻辑、暂时和物理分隔有着敏锐的理解。例如,对于触发服务器端代码执行的用户操作,使用传统ASP的开发人员必须明确地使用户的浏览器将请求返回到Web服务器。创建这样的交互可能会轻易地占用大量开发时间,并且导致不易读的代码。

MicrosoftASP.NET通过使用Web窗体,有助于减轻将用户事件绑定到特定服务器端代码执行的负担,这就模糊了客户端和服务器间的界线。使用ASP.NET和最少的工作,开发人员就可以快速地创建如下的网页,它具有大量的交互式用户界面元素按钮、下拉列表等,而这些都基于最终用户的操作,可以选择性地运行服务器端代码。例如,利用ASP.NET添加下拉列表,只要选定的下拉列表项目更改则执行某些操作,您只需添加DropDownListWeb控件、将其AutoPostBack属性设置为True,然后为该下拉列表创建一个SelectedIndexChanged事件处理程序。如果利用传统的ASP完成上述任务,则会要求编写许多复杂的HTML、客户端JavaScript和服务器端脚本代码;利用ASP.NET,则为您提供了必要的脚本代码和服务器端事件模型。

尽管在执行客户端操作时,ASP.NET中的Web窗体极大地简化了运行服务器端脚本,但是,如果误用这种功能可能会导致无法接受的性能。尽管Web窗体隐藏了所涉及的复杂性,每次需要执行服务器端代码时,最终用户的浏览器必须通过重新提交窗体,将请求返回到Web服务器。当提交窗体时,所有窗体字段(文本框、下拉列表和复选框等)必须同时返回它们的值。此外,页面的视图状态也被返回到Web服务器。总而言之,每次回发网页时,几千字节的数据将需要潜在地发送回Web服务器。于是,经常回发可能很快就会导致Web应用程序不可使用,尤其是对于那些仍然使用拨号连接的用户。通过将功能推到客户端可以降低经常回发的需要。

注ASP.NETWeb窗体发出一个标题为VIEWSTATE的隐藏窗体字段,它包含Web窗体中Web控件已更改状态的基于64位编码的表示。根据出现的Web控件,视图状态的范围可以从几十字节到几万字节。要学习有关视图状态的更多知识,请查阅我的文章UnderstandingASP.NETViewState。

利用传统的ASP,添加数据驱动、自定义客户端脚本非常简单,但并不是非常易读。例如,要在传统的ASP中显示根据某个ID字段加载URL的弹出窗口,您可以使用语法来插入ID字段的值,在适当的客户端脚本中进行输入。ASP.NET允许您利用Page类中的各种方法,创建这种数据驱动的客户端脚本。

本文分析了向ASP.NET网页添加客户端脚本的技术。客户端脚本是运行在访问者浏览器中的脚本代码,如其名字所示。我们将看到如何完成常见的客户端任务,例如显示警告、确认框和弹出窗口。(客户端脚本窗体字段验证的一个主要用途可能与ASP.NET主题有点不相关,因为验证程序Web控件提供了随取随用的客户端窗体验证。)本文的重点在于插入客户端脚本的服务器端类、方法和技术;我们不会详细地分析实际的客户端脚本,因为该信息涉及了围绕Web的众多其他文章和站点。


创建基类作为添加客户端脚本的基础
    传统的ASP和ASP.NET之间的主要差别之一在于各自技术的编程模型。ASP页面是原子的、程序上的脚本,解释每个页面的访问。然而,ASP.NET完全是面向对象的编程技术。所有ASP.NET网页都是带有属性、方法和事件的类。所有网页直接或间接地派生自System.Web.UI命名空间中的Page类,Page类包含了ASP.NET网页的基本功能。

    面向对象编程的概念之一就是继承。继承使您可以创建一个扩展其他类功能的新类。(如果类B继承类A,也可以说扩展了A;类A被称为基类。)当使用代码隐藏模型来创建ASP.NET网页时,可以非常清楚地看到代码隐藏类继承了Page类:

PublicClassWebForm1InheritsSystem.Web.UI.Page...EndClass
通过使您的代码隐藏类继承Page类,它自动接收在Page类中继承的功能,例如Request、Response、Session和ViewState对象以及常见事件,如Init、Load、Render等等。我们将在本文中看到,如果您需要可用于所有ASP.NET网页的某个常见功能,一种方法是创建派生自Page类并具有完成这些所需增强功能的其他方法和属性的类。然后,要使ASP.NET网页利用这些增强功能,我们只需更新页面代码隐藏类中的Inherits语句,以使用扩展Page类的类。

    在本文中,我们将创建一个名为ClientSidePage的类,它派生自Page类并提供额外的方法以协助执行常见的客户端任务。通过让代码隐藏类继承ClientSidePage,而不是继承Page,添加脚本代码将会像调用方法和传送几个参数那样简单。具体说来,该类包括用于下列用途的方法:

• 显示模式客户端对话框。
 
• 在页面加载时将焦点设置到特定的窗体字段。
 
• 使用模式确定对话框来确定用户是否希望回发该窗体。
 
• 显示弹出窗口。
 

    在我们深入研究ClientSidePage类之前,首先让我们分析一下Page类中的有关方法,以便将客户端脚本插入到网页中。在我们讨论这些Page方法后,我们将开始利用ClientSidePage类扩展它们的功能,并且查看如何将所有内容结合在一起以及如何在ASP.NET网页中使用扩展的类。


从代码隐藏类添加客户端脚本
    所有ASP.NET网页必须直接或间接地派生自System.Web.UI命名空间中的Page类。Page类包含正常运行的网页所要求的方法、属性和事件的基本集合。在该类的众多方法中,一些方法旨在将客户端脚本插入到生成的HTML中。这些方法从代码隐藏类调用,因此可以用于发出数据驱动的客户端脚本。用于发出客户端脚本的相关Page类方法如下所示。

     该基类派生自System.Web.UI.Page类,因此可以通过从代码隐藏类直接调用Page类的公共方法来访问它们。

    注要访问Page类的方法,您可以直接键入方法名,或者通过输入MyBase.(对于MicrosoftVisualBasic.NET)、this.(对于C#)或者Page.(对于C#或VisualBasic.NET),利用MicrosoftVisualStudio.NET中的IntelliSense来实现。如果使用VisualBasic.NET作为选择的编程语言,请确保将VisualStudio.NET配置为不隐藏高级成员,否则将无法看到这些客户端脚本方法。(要显示高级成员,请转到Tools|Options|TextEditor|Basic,然后清除Hideadvancedmembers复选框。)

RegisterClientScriptBlock(key,script)
    在Web窗体已呈现的<form>元素之后,在包含于Web窗体中的任意Web控件之前,RegisterClientScriptBlock方法会添加一个客户端脚本块。key输入参数允许您指定与该脚本块相关联的唯一的密钥,而script参数包括要发出的完整的脚本代码。(这个script参数应该包括实际的<script>元素,同时还包括客户端JavaScript或MicrosoftVBScript。)

    在通过ASP.NET网页的代码隐藏类发出客户端脚本时,通常情况下,key参数的值就不是非常重要了。简单地选择一个说明性的密钥值。在通过自定义编译的服务器控件插入客户端脚本代码时,key参数就更加适用。编译的控件有可能需要一组客户端函数。一个页面上服务器控件的多个实例可以共享这些公用客户端脚本函数,因此对于整个页面而言,这些函数只需要发出一次即可,不需要每个控件实例发送一次。例如,验证控件利用客户端代码来增强用户体验。如果页面上存在任意验证控件,这种客户端代码就必须存在,但是如果存在多个验证控件,那么全部控件都可以使用这个单个的共享函数的集合。

    通过为脚本块提供一个密钥,利用公用客户端函数集合构建控件的控件开发人员可以检查所要求的公用函数集合是否已经被页面上控件的另一个实例添加。如果已添加,它不需要重新添加公用脚本。要检查脚本块是否已经使用相同的密钥添加,请使用IsClientScriptBlockRegistered(key)方法,它将返回布尔值,表示带有相同密钥的脚本块是否已经进行了注册。需要注意的是可以在不首先检查它是否注册的情况下添加脚本块。如果尝试利用已经注册的密钥添加脚本块,添加的脚本块将被忽略,并且原来的脚本将保持分配到该密钥。

    注IsClientScriptBlockRegistered方法在以下两种情况下尤为有用。第一,当添加相似但又独特的脚本块时它很方便,您需要确保每个脚本块都给予唯一的密钥。本文稍后分析的代码说明了“isregistered”方法的实用性。第二个用途就是当构建需要某个公用脚本的控件时,尤其是如果特别的生成该脚本。通过使用IsClientScriptBlockRegistered方法,可以确保对页面上服务器控件的所有脚本通用的脚本在每次页面加载时只生成一次,而不是对页面上的每个控件实例都生成一次。

    RegisterClientScriptBlock方法对于添加客户端脚本非常有用,该脚本不依赖于Web窗体内出现的任意窗体字段。该方法的常见使用就是显示客户端警告框。例如,设想您具有一个带有一些TextBoxWeb控件和一个“Save”按钮的网页。TextBox控件可能会具有来自数据库的特殊值。设想该页面允许用户修改这些值并通过单击“Save”按钮提交他们所做的更改。当单击“Save”时,网页将会回发,并且会触发按钮的Click事件。您可以为更新数据库的事件创建一个服务器端事件处理程序。要使用户知道他们的更改已经保存,您可能希望显示一个警告框“Yourchangeshavebeensaved”。通过将以下代码行添加到按钮的Click事件处理程序中,可以实现这个任务:

RegisterClientScriptBlock("showSaveMessage",_"<scriptlanguage=""JavaScript"">_alert('Yourchangeshavebeensaved.');_</script>")
上述代码将会在页面的<form>内添加指定的脚本内容,但是在该窗体的内容前。当在用户浏览器中生成页面时,他们将会看到根据页面加载显示的客户端警告框,如图1所示。

<formmethod="post"...><scriptlanguage="JavaScript">alert('Yourchangeshavebeensaved.');</script>...</form>

图1.客户端JavaScript的结果

    注上面示例中一个潜在地不需要的副作用就是,警告框将会在浏览器接收到<form>标记后立即显示。在用户单击警告框的“OK”按钮之前,浏览器将挂起网页的呈现。这意味着用户在单击“OK”之前,将看到一个空白的浏览器页面。如果希望在显示警告框之前完全显示该页面,您可以使用RegisterStartupScript方法(我们将在下面进行讨论),在<form>元素的结尾处插入JavaScript。

RegisterStartupScript(key,script)
RegisterStartupScript方法与RegisterClientScriptBlock方法非常相似。主要的区别在于发出客户端脚本的位置。在<form>元素开始后,在窗体的内容前,记住用RegisterClientScriptBlock发出脚本。另一方面,RegisterStartupScript在窗体的结尾处、在所有窗体字段后,添加指定的脚本。使用RegisterStartupScript来放置与呈现的HTML元素交互的客户端脚本。(稍后我将研究根据页面加载将焦点设置到窗体字段的示例;要完成这个操作,您将要使用RegisterStartupScript方法。)

与RegisterClientScriptBlock相似,由RegisterStartupScript添加的脚本块需要一个唯一的密钥值。同样,该密钥值主要由自定义控件开发人员使用。并不奇怪,还有一个IsStartupScriptRegistered(key)方法,它返回布尔值,表示带有指定密钥的脚本块是否已经进行了注册。

注有关使用RegisterStartupScript和RegisterClientScriptBlock来创建自定义编译的服务器控件的详细信息,请阅读我以前的文章:InjectingClient-SideScriptfromanASP.NETServerControl.

RegisterArrayDeclaration(arrayName,arrayValue)
如果需要创建带有某些设置值的客户端JavaScriptArray对象,请使用该方法向特定的数组添加值。例如,当使用ASP.NET网页中的验证控件时,就会构建Array对象(Page_Validators),以包含对页面上验证控件集合的引用。当提交窗体时,就会枚举该数组以检查各种验证控件是否有效。

要将值1、2和3添加到名为FavoriteNumbers的客户端Array对象,要使用以下服务器端代码:

RegisterArrayDeclaration("FavoriteNumbers","1")RegisterArrayDeclaration("FavoriteNumbers","2")RegisterArrayDeclaration("FavoriteNumbers","3")
这段代码会发出以下客户端脚本:

<scriptlanguage="javascript"><!--varFavoriteNumbers=newArray(1,2,3);//--></script>
请注意,被传递的每个数组值都必须是字符串;但是,呈现的客户端脚本将Array对象的值设置为字符串的内容。也就是说,如果希望创建带有字符串值“Scott”和“Jisun”的Array,要使用以下代码:

RegisterArrayDeclaration("FavoriteFolks","'Scott'")RegisterArrayDeclaration("FavoriteFolks","'Jisun'")
请注意,第二个输入参数是包含'Scott'和'Jisun'的字符串-文本由单撇号分隔。这会显示以下客户端脚本:

<scriptlanguage="javascript"><!--varFavoriteFolks=newArray('Scott','Jisun');//--></script>
RegisterHiddenField(hiddenFieldName,hiddenFieldValue)
在传统的ASP中,通常需要将各种信息从一个页面分发到另一个页面。完成这个操作的常用方法就是使用隐藏窗体字段。(隐藏窗体字段是不显示的窗体字段,但是它的值会根据窗体的提交而发送。创建隐藏窗体字段的语法是。)在ASP.NET中,通过自定义隐藏窗体字段传递信息的需要会极大地减少,因为页面中的控件状态会自动保持。但是,如果您发现需要创建自定义隐藏窗体字段,可以通过RegisterHiddenField方法来完成。

RegisterHiddenField方法接受两个输入参数。隐藏字段的名称和值。例如,要创建带有名称foo和值bar的隐藏窗体字段,请使用以下代码:

RegisterHiddenField("foo","bar")
这会在页面的<form>元素中添加隐藏窗体字段,如下所示:

<formname="_ctl0"method="post"action="test.aspx"id="_ctl0"><inputtype="hidden"name="foo"value="bar"/>...</form>
理解客户端元素是如何呈现的
Page类包含负责呈现在上面讨论的方法中注册的客户端脚本的两个internal方法:OnFormRender和OnFormPostRender。(标记为internal的方法只能被相同程序集中的其他类调用。因此,无法从ASP.NETWeb应用程序中的代码隐藏类调用Page的internal方法。)这两个方法都在HtmlForm类的RenderChildren方法中进行调用。位于System.Web.UI.HtmlControls命名空间中的HtmlForm类表示Web窗体;也就是说,ASP.NET网页中的服务器端窗体<formrunat="server">...</form>在页面的实例化阶段中作为HtmlForm类的实例加载。

因为由Page类的众多方法注册的客户端脚本是在OnFormRender和OnFormPostRender方法中呈现的,并且因为这些方法只能由HtmlForm类进行调用,所以通过这些方法以编程方式添加的客户端脚本,只有在网页包含Web窗体时才会呈现。也就是说,通过上面讨论的任意方法以编程方式添加的任意脚本元素,在ASP.NET网页包含服务器端窗体(<formrunat="server">...</form>)时只会在页面的最后标记中发出。

通过首先添加开始<form>元素,会呈现ASP.NET网页上的Web窗体。之后,会调用Web窗体的RenderChildren方法,它包含三行代码:

Page.OnFormRender(...)MyBase.RenderChildren(...)Page.OnFormPostRender(...)
对Page类的OnFormRender方法的调用会添加以下标记:

• 通过对RegisterHiddenField进行调用而添加的任意隐藏窗体字段。
 
• 隐藏窗体字段中名为__VIEWSTATE的基于64位编码的视图状态。
 
• 通过对RegisterClientScriptBlock进行调用而添加的任意脚本块。
 

Web窗体的RenderChildren方法中的第二行代码调用基类的RenderChildren方法,它会在Web窗体中呈现内容。在呈现所有窗体的内容后,对Page类的OnFormPostRender方法进行调用,这将会添加以下客户端内容:

• 由RegisterArrayDeclaration方法添加的任意Array对象。
 
• 通过对RegisterStartupScript进行调用而添加的任意脚本块。
 

最后,在Web窗体的RenderChildren方法完成后,则会呈现关闭窗体标记()。图2以图表形式说明了这个呈现过程。

注图2假设您有些熟悉ASP.NET页面的生命周期。如果您对学习更多有关页面生命周期的知识感兴趣,请考虑阅读UnderstandingASP.NETViewState,将重点放在标题为“TheASP.NETPageLifeCycle”的节上。


图2.ASP.NET中呈现的页面

分析脚本块的呈现顺序
乍看Page类的注册方法,在网页中呈现的已注册元素的顺序好像是对应于它们被添加到代码中的顺序。也就是说,设想ASP.NET网页的代码隐藏类中有以下两行代码:

RegisterClientScriptBlock("showSaveMessage",_"<scriptlanguage=""JavaScript"">varname='"&_someDataDrivenValue&"';</script>")RegisterClientScriptBlock("showSaveMessage",_"<scriptlanguage=""JavaScript"">alert('Hello,'+name+'!');</script>")
当发现页面呈现以下客户端脚本块(假设someDataDriveValue的值为Sam)时,您不要太奇怪:

<scriptlanguage="JavaScript">varname='Sam';</script><scriptlanguage="JavaScript">alert('Hello,'+name+'!');</script>
访问该页面的用户将会看到一个警告框显示“Hello,Sam!”。

基于这个测试,您可能会认为这样的事实始终成立:在HTML页面中发出脚本块的顺序就是在服务器端代码中为它们指定的顺序。但是,这是一个不正确的假设,并且可以导致页面中断。例如,设想前面添加的脚本块在HTML页面中以相反的顺序发出。那么,您将会得到:

<scriptlanguage="JavaScript">alert('Hello,'+name+'!');</script><scriptlanguage="JavaScript">varname='Sam';</script>
这将会显示警告框“Hello,!”,因为变量name尚未分配值。显然,有些时候发出脚本块的顺序非常重要的。

Page类的注册方法-RegisterClientScriptBlock、RegisterStartupScript、RegisterArrayDeclaration和RegisterHiddenFields-全部将提供的脚本内容写入到内部HybridDictionary中。HybridDictionary是在System.Collections.Specialized命名空间中发现的数据结构,设计用于在有很多项目未知的字典中存储项目。对于小型的项目集合,ListDictionary是最有效的数据结构,但是对于较大的字典,Hashtable会更有效。HybridDictionary分离差异-它通过使用ListDictionary存储项目来开始。当ListDictionary添加了它的第九个项目后,HybridDictionary会从使用ListDictionary切换到使用Hashtable。

尽管这种方法对于性能而言非常理想,但是如果您使用几个脚本块而且脚本块的顺序非常重要,那么它可能会带来严重的破坏。这是因为,ListDictionary维护元素被添加的顺序,而Hashtable没有进行维护。因此,如果您将八个或更少项目添加到任意一个特定的注册方法中,项目将会以它们被添加的顺序发出。但是,如果添加了第九个项目,脚本发出的顺序看起来将是随机的。

注ListDictionary使用linkedlist存储它的元素,而Hashtable将它的元素存储在数组中,该数组的内容按照字符串键的哈希值进行排序。有关链接列表和哈希表的完整讨论已经远远超出了本文所讨论的范围。有关详细信息,包括对其性能的分析,请考虑阅读AnExtensiveExaminationofDataStructures,尤其是Part2和Part4。

如果您计划出现如下情况:可能会存在使用特殊注册方法添加的多于八个客户端元素,并且元素的出现顺序比较重要,那么您可能希望研究一下PeterBlum的免费的RegisterScripts库。对于所发出客户端元素的顺序,RegisterScripts提供了更大的控制能力,并且还提供了无需手动添加<script>标记的选项,当利用RegisterClientScriptBlock或RegisterStartupScript方法包括客户端脚本时,您必须添加此标记。

返回页首
根据对用户操作的响应执行客户端代码
对于插入页面加载时运行的客户端代码而言,Page类的注册方法非常理想,但是在很多情况下,我们希望根据对最终用户操作的响应来运行代码。例如,我们可能希望当用户单击按钮时显示确认对话框,或者当下拉列表的选定项目发生变化时调用特殊的客户端JavaScript功能。

HTML元素具有您可以点击的大量客户端事件,并且当触发事件时可以执行客户端代码。所要求的标记只是在HTML元素的标记中作为一个属性。例如,要在单击某个按钮时显示警告框,可以添加以下代码:

<inputtype="button"value="Clickmetoseeanalertbox!"onclick="alert('Hereitis!');"/>
要在客户端事件触发时运行客户端代码,可以将适当的属性添加到HTML元素中。对于Web控件,可以借助于编程方式使用Attributes集合添加客户端属性。例如,设想您具有一个TextBoxWeb控件,只要呈现的文本框获得焦点,您就希望它突出显示为黄色。要完成上述操作,您要将TextBoxWeb控件的呈现HTML添加如下所示的代码:

<inputtype="text"onfocus="this.style.backgroundColor='yellow';"onblur="this.style.backgroundColor='white';"/>
要完成这个标记,我们可以借助编程方式通过Attributes集合设置TextBoxWeb控件的onfocus和onblur客户端属性,如下所示:

TextBoxControl.Attributes("onfocus")="this.style.backgroundColor='yellow';"TextBoxControl.Attributes("onblur")="this.style.backgroundColor='white';"
将客户端代码与客户端事件结合在一起的这种技术通常用于提供丰富的、交互式的用户体验。稍后,我们将会在本文中说明如何使用这种技术来显示基于用户操作的确认对话框。

返回页首
实现常用客户端功能
在我们研究ASP.NET方法(涉及动态地将客户端脚本添加到网页)后,让我们将注意力转移到这个知识。本文的其余部分重点讲述常用客户端任务,例如显示警告框、确认框、弹出窗口等等。具体说来,我们将创建包含一组方法的类,这组方法可用在ASP.NET项目中,从而快速、便捷地提供这样的功能。

我们将要分析的、贯穿本文剩余部分的VisualBasic.NET代码可以在本文的代码下载中获得。

显示警告框
一个常用的客户端要求就是显示警告框。警告框是一个客户端模式对话框,通常用于为最终用户提供某些重要的信息。警告框的示例如图1所示。警告框是通过客户端JavaScriptalert函数来进行显示的,该函数接受一个单个的参数,即要显示的消息。显示警告框相当简单和直接;实际上,本文前面已经显示了一个示例。

为了使页面开发人员尽可能简单的显示警告框,让我们创建一个名为ClientSidePage的类,其中包含一个名为DisplayAlert(message)的方法。这个类将会继承Page类。想要利用这些客户端helper方法的页面开发人员需要使他们的代码隐藏类继承这个ClientSidePage类,而不是继承默认的Page类。以下代码显示了带有其第一个方法DisplayAlert的ClientSidePage类。

PublicClassClientSidePageInheritsSystem.Web.UI.PagePublicSubDisplayAlert(ByValmessageAsString)RegisterClientScriptBlock(Guid.NewGuid().ToString(),_"<scriptlanguage=""JavaScript"">"&GetAlertScript(message)&"</script>")EndSubPublicFunctionGetAlertScript(ByValmessageAsString)AsStringReturn"alert('"&message.Replace("'","\'")&"');"EndFunctionEndClass
请注意,这个类派生自System.Web.UI.Page类。DisplayAlert方法只是使用RegisterClientScriptBlock方法,在警告框中显示提供的message。由于这个方法可能会由单个页面多次调用,每个调用将使用其密钥的GUID(是“全局唯一标识符”的首字母缩写)。传递到alert函数的字符串会使用撇号分隔,message中的任意撇号都必须进行转义(JavaScript将撇号转义为\')。

要将这段代码用于ASP.NETWeb应用程序中,您需要将新类添加到ASP.NET应用程序中。在VisualStudio.NET中,右键单击解决方案资源管理器中的ASP.NETWeb应用程序项目名,然后选择添加新类。然后,剪切上述代码并将其粘贴到该类中。接下来,在要利用该代码的ASP.NET网页中,您需要修改代码隐藏类,以便它从ClientSidePage类而不是从Page中继承。以下代码显示了派生自ClientSidePage并使用DisplayAlert方法的示例代码隐藏类。

PublicClassWebForm1InheritsClientSidePagePrivateSubPage_Load(ByValsenderAsSystem.Object,_ByValeAsSystem.EventArgs)_HandlesMyBase.LoadDisplayAlert("Hello,World!")EndSub...EndClass
请注意,ClientSidePage类不仅具有可以生成完整客户端<script>元素的DisplayAlert方法,还具有可以返回客户端脚本无<script>标记的GetAlertScript方法。第二个方法可用于您希望基于某个客户端事件显示警告的情况中。例如,如果您希望在特定文本框接收到焦点的任意时间显示警告,可以将以下代码添加到服务器端代码隐藏类中:

TextBoxControlID.Attributes("onfocus")=GetAlertScript(message)
将焦点设置为页面加载时的窗体字段
您是否注意到当访问Google时,焦点自动设置在搜索文本框呢?这一点小的“功能”使得搜索Google更快-在访问Google时您不必再花费时间移动鼠标,然后单击文本框。更确切的说,您只需在页面加载时键入即可。将焦点设置到窗体字段(可以是文本框、单选按钮、复选框或下拉列表)只要求几行客户端JavaScript代码。让我们将方法添加到ClientSidePage类,该类将在页面加载时自动向指定的Web控件中添加焦点。这种方法需要发出如下所示的客户端脚本:

<scriptlanguage="JavaScript">functionCSP_focus(id){varo=document.getElementById(id);if(o!=null)o.focus();}</script>...Formfields...<inputtype="..."id="idofelementtofocus".../>...Formfields...<scriptlanguage="JavaScript">CSP_focus(idofelementtofocus);</script>
客户端函数CSP_focus接受字符串参数,窗体字段的ID被设置为焦点,并且从DOM中检索HTML元素。然后,调用检索元素的focus()函数。在网页的底部,在指定所有窗体字段后,我们需要调用CSP_focus方法,该方法在想要设置焦点的窗体字段的ID中传递。

下面的方法GiveFocus(Control)使用RegisterClientScriptBlock和RegisterStartupScript方法来生成所需要的客户端脚本。

PublicSubGiveFocus(ByValcAsControl)RegisterClientScriptBlock("CSP-focus-function",_"<scriptlanguage=""JavaScript"">"&vbCrLf&_"functionCSP_focus(id){"&_"varo=document.getElementById(id);"&_"if(o!=null)o.focus();"&_"}"&vbCrLf&_"</script>")RegisterStartupScript("CSP-focus",_"<scriptlanguage=""JavaScript"">CSP_focus('"&_c.ClientID&"');</script>")EndSub
要从其代码隐藏类继承ClientSidePage的ASP.NET网页中使用GiveFocus方法,可以简单地在Page_Load事件处理程序中调用GiveFocus,并传递应该在页面加载时设置其焦点的Web控件。例如,要将焦点设置为TextBoxWeb控件TextBoxControl,请使用以下Page_Load事件处理程序:

PrivateSubPage_Load(ByValsenderAsSystem.Object,_ByValeAsSystem.EventArgs)_HandlesMyBase.LoadGiveFocus(TextBoxControl)EndSub
打开弹出窗口
尽管弹出窗口作为广告发布者的工具在Internet上早已臭名昭著,但是很多Web应用程序还是因为使用弹出窗口而得到了好处。例如,您想要某个页面在DataGrid中显示数据库项目的列表,同时带有可以编辑每个特定项目的链接。不再使用DataGrid的内联编辑功能,您可能希望在用户选择编辑DataGrid时打开弹出窗口,其中弹出窗口包含带有可编辑DataGrid字段的文本框列表。(您希望这么做的一个原因在于可能存在大量的可编辑字段,但是您只想显示DataGrid中最适当的字段,因此要消除使用DataGrid的内置编辑功能的可能性。)

要显示弹出窗口,请使用JavaScript函数window.open(),它使用很多可选输入参数,其中三个密切相关的参数是:

• 加载弹出窗口的URL。
 
• 弹出窗口的字符串名称。
 
• 弹出窗口的特性,例如高度和宽度、窗口是否可以调整大小等等。
 

window.open()函数的完整讨论已经超出了本文的范围;要学习更多内容,请参阅技术文档。

与其他显示警告框的方法相似,ClientSidePage类包含用于显示弹出窗口的两个方法-一个呈现显示窗口的自包含<script>块,另一个仅返回JavaScript脚本本身。除了打开弹出窗口的方法外,还有一组关闭当前窗口的方法。(可能会出现这样的情况,您希望以编程方式基于某些客户端或服务器端事件来关闭弹出窗口。)

PublicSubDisplayPopup(ByValurlAsString,ByValoptionsAsString)RegisterStartupScript(Guid.NewGuid().ToString(),_"<scriptlanguage=""JavaScript"">"&_GetPopupScript(url,options)&_"</script>")EndSubPublicFunctionGetPopupScript(ByValurlAsString,_ByValoptionsAsString)AsStringReturn"varw=window.open("""&_url&""",null,"""&options&""");"EndFunctionPublicSubCloseWindow(OptionalByValrefreshParentAsBoolean=False)RegisterClientScriptBlock("CSP-close-popup",_"<scriptlanguage=""JavaScript"">"&_GetCloseWindowScript(refreshParent)&"</script>")EndSubPublicFunctionGetCloseWindowScript(Optional_ByValrefreshParentAsBoolean=False)AsStringDimscriptAsStringIfrefreshParentThenscript&="window.opener.location.reload();"EndIfReturn"self.close();"EndFunction
该代码的执行示例可以在本文的代码下载中找到。同时,您还将发现一个示例网页,它具有一个在与ASP.NET网页相同的目录中列出文件的DataGrid。这个DataGrid具有两列:显示超级链接的TemplateColumn,当单击时打开显示所选文件内容的弹出窗口;以及该文件的名称(如图3所示)。


图3.带有弹出窗口的DataGrid

DataGrid的标记利用GetPopupScript方法,如下所示:

<asp:DataGridid="dgFiles"runat="server"...><Columns><asp:TemplateColumnHeaderText="View"><ItemTemplate><ahref='javascript:<%#GetPopupScript("ViewFile.aspx?FileName="&DataBinder.Eval(Container.DataItem,"Name"),"scrollbars=yes,resizable=yes,width=500,height=400")%>'>ViewFile</a></ItemTemplate></asp:TemplateColumn><asp:BoundColumnDataField="Name"HeaderText="Filename"></asp:BoundColumn></Columns></asp:DataGrid>
ASP.NET网页ViewFile.aspx打开其名称在querystring中指定的文件并显示其内容(如图4所示)。


图4.在弹出窗口中显示Web.config的内容

注弹出窗口最适用于只有Intranet应用程序的情况,因为很多Internet用户利用某种弹出阻止软件,例如Google工具栏。实际上,利用MicrosoftWindowsXPServicePack2,MicrosoftInternetExplorer将会在默认情况下配置为阻止弹出窗口。但是,当用户访问受信任站点或本地Intranet区域中的站点时,弹出窗口将仍然会出现。有关在WindowsXPServicePack2中阻止InternetExplorer弹出窗口功能的详细信息,请务必阅读ChangestoFunctionalityinMicrosoftWindowsXPServicePack2。

在回发前确认
在本文的前面部分,我们研究了如何显示客户端警告框,这是带有“OK”按钮的模式对话框。JavaScript提供被称为确认对话框的更具有交互风格的警告框。使用confirm(message)函数显示确认对话框并通过message输入参数与“OK”和“Cancel”按钮指定显示带有文本的对话框的效果。如果用户单击“OK”,confirm(message)函数会返回true;如果他们单击“Cancel”,则返回false。

通常情况下,确认对话框用于确保用户在提交窗体之前希望继续。当单击HTML元素(例如提交按钮)提交窗体时,如果HTML元素触发返回false的客户端事件处理程序,窗体提交就被取消。通常,确认对话框用于网页中,如下所示:

<form...><inputtype="submit"value="ClickMetoSubmittheForm"onclick="returnconfirm('Areyousureyouwanttosubmitthisform?');"/></form>
当用户单击“ClickMetoSubmittheForm”按钮时,他们将会看到确认对话框,询问他们是否确实希望提交该窗体(如图5所示)。如果用户单击“OK”,confirm()将返回true,该窗体将被提交。但是,如果他们单击“Cancel”按钮,confirm()将返回false,该窗体提交将被取消。


图5.确认JavaScript的结果

设想您具有一个DataGrid带有标签为“Delete”的一列按钮。在单击该按钮时,该窗体将会回发,并且选定的记录将被删除。在此例中,您可能希望复查用户是否确实希望删除该记录。此时,要使用客户端确认对话框将会非常理想。您可以用对话框提示用户,声明如下所示的内容:“Thiswillpermanentlydeletetherecord.Areyousureyouwanttocontinue?”如果用户单击“OK”,该窗体将会回发,并且记录将被删除;如果他们单击“Cancel”,该窗体将不会回发,而且记录也不会被删除。

要添加在单击按钮后显示确认对话框所必需的客户端JavaScript,请简单地使用Attributes集合来添加客户端onclick事件处理程序。具体说来,要将onclick事件处理程序代码设置为:returnconfirm(message);。为了提供DataGrid的ButtonColumn的这种功能,您需要在DataGrid的ItemCreated或ItemDataBound事件处理程序中以编程方式引用Button或LinkButton控件,并且在那里设置onclick属性。有关详细信息,请参阅http://aspnet.4guysfromrolla.com/articles/090402-1.aspx

确认AutoPostBackDropDownLists
尽管通常在单击按钮时使用确认对话框,但是还可以在更改下拉列表时使用它们。例如,您可能具有一个当特定的DropDownListWeb控件发生更改时会自动回发的网页。(DropDownListWeb控件具有一个AutoPostBack属性,如果设置为True,只要DropDownList的选定项目发生更改就会导致窗体回发。)

直观地讲,您可能认为对DropDownList添加确认对话框与对ButtonWeb控件添加这种对话框相同。也就是说,简单地将DropDownList的客户端onchange属性更改为如下内容:returnconfirm(...);。使用:

DropDownListID.Attributes("onchange")="returnconfirm(...);"
遗憾的是,这并不会按期望工作,因为AutoPostBackDropDownList的onchange属性将设置为会导致回发的JavaScript,即对客户端__doPostBack函数的调用。当您自己借助编程方式设置onchange属性时,最后的结果是呈现的客户端onchange事件处理程序同时具有您的代码和对__doPostBack的调用:

<selectonchange="returnconfirm(...);__doPostBack(...);">...</select>
记住,我们确实希望发生的情况是,如果确认返回true,就调用__doPostBack函数,因为之后页面将会被回发。通过利用Attributes集合将onchange事件设置为:if(confirm(...)),我们可以完成这一操作,而该代码会生成以下标记,该标记正是我们所希望的:

<selectonchange="if(confirm(...))__doPostBack(...);">...</select>
乍看起来,这似乎会具有所期望的效果。如果用户从下拉列表中选择不同的项目,将会出现一个确认框。如果用户单击“OK”,该窗体将回发;如果用户单击“Cancel”,该窗体回发会暂停。尽管问题在于下拉列表维持用户选定的项目以启动下拉列表的onchange事件。例如,设想下拉列表加载正在进行选择的项目x,然后用户选择项目y。这将会触发下拉列表客户端onchange事件,它将会显示确认对话框。现在,设想用户点击“Cancel”-下拉列表将仍然选择项目y。我们希望的是将选择转回到项目x。

要实现此目的,我们需要做两件事情:

1.
 编写一个“记住”选定下拉列表项目的JavaScript函数。
 
2.
 在下拉列表的客户端onchange事件中,如果用户单击“Cancel”,您需要将下拉列表转换回“已记住的”值。
 

步骤1必须为下拉列表和函数(当页面加载时运行,并且记录下拉列表的值)创建全局脚本变量。步骤2要求为下拉列表的客户端onchange属性更改为如下所示内容:

if(!confirm(...))resetDDLIndex();else__doPostBack();
其中resetDDLIndex是JavaScript函数,它将下拉列表选定的值返回到“已记住的”值。用于此目的的客户端脚本应该如下所示:

<selectid="ddlID"onchange="if(!confirm(...))resetDDLIndex();else__doPostBack(...);">...</select><scriptlanguage="JavaScript">varsavedDDLID=document.getElementById("ddlID").value;functionresetDDLIndex(){document.getElementById("ddlID").value=savedDDLID;}</script>
通过在ClientSidePage类中创建helper方法,这个必要的脚本可以轻松地生成。

PublicSubConfirmOnChange(ByValddlAsDropDownList,ByValmessageAsString)'RegisterthescriptblockIfNotIsStartupScriptRegistered("CSP-ddl-onchange")ThenRegisterStartupScript("CSP-ddl-onchange",_"<scriptlanguage=""JavaScript"">"&_"varCSP_savedDDLID="&_document.getElementById('"&_ddl.ClientID&"').value;"&vbCrLf&_"functionresetDDLIndex(){"&vbCrLf&_"document.getElementById('"&"&_"ddl.ClientID&"').value=CSP_savedDDLID;"&vbCrLf&_"}"&vbCrLf&_"</script>")EndIfddl.Attributes("onchange")=_"if(!confirm('"&message.Replace("'","\'")&_"'))resetDDLIndex();else"EndSub
要使用这段代码,简单地调用网页上每个AutoPostBackDropDownList的该方法,当网页上的选定项目发生更改时要在该网页上显示对话框。

未保存而退出时进行确认
在我所创建的大多数每个数据驱动的Web应用程序中,始终会有用户可以编辑数据库特定信息的特定页面。非常简单的一个示例是带有一系列TextBox和DropDownListWeb控件的页面,而数据库数据填充在这些控件中。用户可以进行任何适当的修改,然后单击“Save”按钮将他们所做的更改保存到数据库。

当我创建这些页面时,通常会以两个ButtonWeb控件来结束页面:“Save”按钮和“Cancel”按钮。“Save”按钮将任意更改保存回数据库,而“Cancel”按钮不保存任何更改退出页面。尽管两个按钮看起来可能是一个完美的设计,但有时用户会在他们想要单击“Save”按钮时意外地单击“Cancel”按钮,这样就会丢失了他们对数据所做的所有更改。为防止这种情况发生,可以在“Cancel”按钮上使用确认框,它只有在网页上的任意文本框或下拉列表发生更改时才会出现。也就是说,如果用户对数据进行了任意更改,然后单击“Cancel”,确认框将会提示他们是否确实要在不保存的情况下退出。(如果用户只是单击“Cancel”,而没有更改任何数据,将不会显示这样的确认框。)

这种用户体验可以通过少量客户端JavaScript来实现。基本上可以说,它需要一个JavaScript全局变量isDirty,在初始时为false,但只要触发窗体字段的onchange事件,它就会设置为true。如果isDirty为true,则还有一个显示确认对话框的JavaScript函数。“Cancel”按钮的onclick客户端事件处理程序限定为从该JavaScript函数返回结果。以下HTML说明了这个概念:

<scriptlanguage="JavaScript">varisDirty=false;functioncheckForChange(msg){if(isDirty)returnconfirm(msg);elsereturntrue;}</script>Name:<inputtype="text"onchange="isDirty=true;"/><inputtype="submit"name="btnSave"value="Save"id="btnSave"/><inputtype="submit"name="btnCancel"value="Cancel"id="btnCancel"onclick="returncheckForChange('Youhavemadechangestothedatasincelastsaving.Ifyoucontinue,youwilllosethesechanges.');"/>
可以通过将该脚本生成移动到ClientSidePage类,简单地生成这个脚本。具体说来,我们可以创建下列三个方法:

ProtectedSubRegisterOnchangeScript()IfNotIsClientScriptBlockRegistered("CSP-onchange-function")ThenRegisterClientScriptBlock("CSP-onchange-function",_"<scriptlanguage=""JavaScript"">"&_"varisDirty=false;"&vbCrLf&_"functionCSP_checkForChange(msg){"&vbCrLf&_"if(isDirty)returnconfirm(msg);"&_"elsereturntrue;"&vbCrLf&_"}"&vbCrLf&_"</script>")EndIfEndSubPublicSubMonitorChanges(ByValcAsWebControl)RegisterOnchangeScript()IfTypeOfcIsCheckBoxOrTypeOfcIsCheckBoxList_OrTypeOfcIsRadioButtonListThenc.Attributes("onclick")="isDirty=true;"Elsec.Attributes("onchange")="isDirty=true;"EndIfEndSubPublicSubConfirmOnExit(ByValcAsWebControl,ByValmessageAsString)RegisterOnchangeScript()c.Attributes("onclick")=_"returnCSP_checkForChange('"&message.Replace("'","\'")&"');"EndSub
要创建表现这个行为的网页,我们只需要将其服务器端代码隐藏类派生自ClientSidePage,并且在Page_Load事件处理程序中,为需要客户端onchange事件的每个Web控件对MonitorChanges进行调用,并且为在单击时应该显示警告用户是否进行更改并退出页面的每个Button、LinkButton和ImageButton,对ConfirmOnExit进行调用。

注MonitorChanges方法使用onclick客户端事件,而不是用于CheckBox、CheckBoxList和RadioButtonListWeb控件的onchange。这是因为这些控件将<span>标记或<table>限制在复选框或很多复选框或单选按钮附近。在我利用InternetExplorer进行测试时,我发现在应用到<span>或<table>时,选中复选框或单击单选按钮并没有选择onchange事件,但是却触发了onclick事件。

图6显示了带有两个TextBoxWeb控件、一个DropDownListWeb控件和一个CheckBoxWeb控件的ASP.NET网页示例。如下面的Page_Load事件处理程序所示,所有这些Web控件都会被监视所做的更改。配置“Cancel”按钮btnCancel,这样如果在进行更改后单击它,将显示一个确认对话框。


图6.带有确认的对话框

PublicClassConfirmOnExitInheritsClientSidePagePrivateSubPage_Load(ByValsenderAsSystem.Object,_ByValeAsSystem.EventArgs)_HandlesMyBase.Load'SpecifywhatcontrolstocheckforchangesMonitorChanges(name)MonitorChanges(age)MonitorChanges(favColor)MonitorChanges(chkSilly)ConfirmOnExit(btnCancel,_"Youhavemadechangestothedatasincelastsaving."&_"Ifyoucontinue,youwilllosethesechanges.")EndSub...EndClass
注客户端onchange事件不能用于Netscape的旧版本中。同样,InternetExplorer5.0的onchange事件也具有一些已报告的问题(在InternetExplorer5.01SP1中已经修复)。

而且,这种方法将不会像利用DropDownListWeb控件那样将AutoPostBack设置为True,而是回发将重置isDirty的值。这个问题有很多解决方案,例如使用隐藏窗体字段,指示出回发窗体数据是否以dirty开始。我将实现这个操作的过程作为练习留给读者。

创建客户端MessageBox控件
由于确认对话框是一种防止意外点击的非常好的方法,可以潜在地降低到Web服务器的回发数量,有几种特定方案您可能希望显示确认对话框,并且能够在服务器端确定用户是否单击了“OK”或“Cancel”。(记住,对于确认对话框,如果用户单击“Cancel”,该窗体则不会回发。)而且,JavaScript中的警告和确认框在外观上非常受限。幸运的是,客户端VBScript通过其MsgBox函数提供了更丰富的消息框体验。

在过去的项目中,我需要客户端模式消息框,无论单击什么按钮,它都可以引起回发。作为响应,我构建了自定义编译的ASP.NET服务器控件来满足这些要求。此外,客户端消息框还使用VBScript的MsgBox函数来提供更丰富的消息框体验。

注在MicrosoftInternetExplorer浏览器中,VBScript只作为客户端脚本编辑语言。要考虑到这一点,如果访问浏览器是InternetExplorer,那么我的服务器控件只能使用VBScript。如果是非InternetExplorer浏览器,则服务器端控件使用JavaScript。

对这种自定义服务器控件深入的讨论可以完全保证整个文章的正确性,因此无需将重点放在控件的内部工作原理上,让我们分析如何在ASP.NET网页中使用MessageBox控件。(控件的完整资源以及使用该控件的示例ASP.NET网页都可以从本文的下载中获得。)

要在ASP.NET网页中使用MessageBox控件,首先要将MessageBox控件添加到VisualStudio.NET工具箱。通过右键单击工具箱、从工具箱中选择“Add/RemoveItems”、然后浏览到MessageBox程序集可以实现上述任务。要将客户端消息框添加到网页,只要将其从工具箱拖动到该设计器即可。图7显示了VisualStudio.NET设计器中的MessageBox控件。


图7.显示模式消息框

MessageBox类具有很多可以进行配置的属性,以调整消息框的外观:

• Buttons.指定要显示的按钮。ButtonOptions枚举中定义的选项可以为:OkOnly、OkCancel、AbortRetryIgnore、YesNoCancel、YesNo或RetryCancel。
 
• DisplayWhenButtonClicked.点击后将显示客户端消息框的按钮Web控件的ID。如果希望由于点击某个特定按钮而显示消息框,请使用这个属性。
 
• Icon.显示在消息框中的图标;选项定义于IconOptions枚举中。有效的值为:Critical、Question、Exclamation和Information。
 
• Prompt.显示在消息框中的文本。
 
• Title.消息框的标题。
 

一旦将MessageBox控件添加到ASP.NET网页,下一个挑战就是使其根据特定客户端操作进行显示。DisplayWhenButtonClicked属性允许您指定页面上按钮Web控件的ID点击后将会显示消息框。另外,您还可以通过调用客户端函数mb_show(id)来显示消息框,其中ID是MessageBox控件的ID。

不管您选择在消息框中显示什么按钮配置,当单击任意按钮时,随后就会发生回发并且触发MessageBox控件的Click事件。通过在设计器中简单地双击MessageBox,可以为此事件创建一个事件处理程序。事件处理程序的第二个输入参数是类型MessageBoxClickedEventArgs,它包含一个返回用户单击消息框按钮的信息的ButtonClicked属性。

MessageBox控件在如下情况下非常有用,当您希望快速为用户提供模式对话框,而不管用户的选择以及回发中的结果。要查看操作中的MessageBox控件,请签出源代码下载中的MsgBoxDemo.aspx页面。

返回页首
小结
本文一开始就研究了网页中客户端脚本的常见使用,然后转向分析将客户端脚本插入到ASP.NET网页中的方法和技术。正如我们看到的那样,Page类包含很多旨在借助编程方式从服务器端代码隐藏类插入客户端脚本块的方法。这些方法也常用于自定义编译的服务器控件,请参阅我以前文章中的讨论:InjectingClient-SideScriptfromanASP.NETServerControl.

除了添加脚本块外,客户端功能通常必须与由某些HTML元素引发的客户端事件结合在一起。要借助编程方式通过服务器端代码隐藏类指定Web控件的客户端事件处理程序,请使用Attributes集合,它可用作所有Web控件的属性。

本文的后半部分应用了前半部分中涉及的内容,显示了如何在ASP.NET网页中实现常用客户端功能。我们看到了如何扩展Page类,这样我们可以利用代码隐藏类轻松地显示警告框、将页面加载上的焦点设置到特定Web控件、如何显示弹出窗口以及如何显示确认对话框。我们还研究了创建自定义服务器控件,它使用VBScript来提供更丰富的客户端消息框用户体验,而且无需考虑单击按钮就可以导致回发。

祝大家编程愉快!

特别感谢...
在我将文章提交给MSDN编辑之前,有很多志愿者帮助我对本文进行校对并为本文的内容、语法和目的提供了反馈。本文校对过程中的主要贡献者包括MaximKarpov、CarlosSantos、MilanNegovan和CarlLambrecht。如果您有兴趣加入到不断增长的校对人员中,请给我发邮件mitchell@4guysfromrolla.com

posted on 2006-03-09 15:05  堕落的卖猪贩  阅读(447)  评论(0编辑  收藏  举报