
3.8 包含外部的JavaScript文件
理论上,UpdatePanel控件中可放入大部分的ASP.NET控件,这也包含了使用者自定义的UserControl控件,但是这有个异常情况,当该UserControl中含有JavaScript程序代码,且被动态加载至UpdatePanel控件中时会引发一些问题。呃!动态加载UserControl至UpdatePanel中,这不是跟前面提及的动态加载控件一样吗?不过这次我们采用另一种方式,用完全式的动态加载,也就是说一开始UserControl并不是放在UpdatePanel控件中的,而是在使用者点击按钮后,由程序动态加载的。请照以下的步骤来创建一个新网页。
1. 创建一个UserControl,取名为WebUserControl.ascx,程序代码如程序3-18所示。
2. 创建一个新网页,命名为UseUserControl.aspx。
3. 在页面中放入ScriptManager控件。
4. 在页面中放入UpdatePanel控件。
5. 将UpdatePanel1控件的UpdateMode设为Conditional。
6. 放此WebUserControl控件至UpdatePanel控件中。
程序3-18
Samples\3\AjaxDemo1\WebUserControl.ascx <%@ Control Language="C#" AutoEventWireup="true" CodeFile= "WebUserControl.ascx.cs" Inherits="WebUserControl" %> <script language=javascript> function showMessage(msg) { alert('Message:'+msg); } </script> <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" OnClientClick="showMessage('Test')" />
运行程序后,点击Button后便可看到信息,如图3-15所示。

图3-15
这个例子证明了UserControl可以放在UpdatePanel控件中,接下来就是将此例子改成动态加载UserControl。请照以下步骤做。
1. 创建一个新网页,命名为DynamicLoadUserControl1.aspx。
2. 在页面中放入一个ScriptManager控件。
3. 在页面中放入一个UpdatePanel控件。
4. 放一个Button控件至UpdatePanel控件中。
5. 在Button的Click事件中键入程序3-19的代码。
程序3-19
Samples\3\DynamicLoadUserControl1.aspx protected void Button1_Click(object sender, EventArgs e) { if (UpdatePanel1.ContentTemplateContainer.FindControl("DynamicControl1") == null) { Control c = Page.LoadControl("WebUserControl.ascx"); c.ID = "DynamicControl1"; UpdatePanel1.ContentTemplateContainer.Controls.Add(c); } }
运行程序并点击Button后,便可看到该UserControl控件已被载入,这是无刷新动态加载UserControl的基础,如图3-16所示。

图3-16
但若点击右方的Button后,会看到报错信息,如图3-17所示。

图3-17
这是为什么呢?是因为UpdatePanel控件的处理机制。只要异步刷新的内容含有JavaScript程序代码,这段程序代码是无法被绘制到现行网页中的。别误会!我指的是明白声明成<script>...</script>的程序代码,以及<script src....></script>类的程序代码,不包含直接于HTML元素中所设定的程序代码,例如onclick='alert(...)'类的程序代码是被允许的,但是若这类程序代码调用其他自定义于以上两种区段的函数时,同样是行不通的!就如同本例中,showMessage是声明于<script>区段中的,虽然onclick可以正常绘制,但因为所调用的JavaScript函数未被绘制,所以引发调用函数不存在的错误。这个特性也告诉了我们,UpdatePanel控件中所能动态加载的ASP.NET控件类型,只要该ASP.NET控件会输出以上两种区段JavaScript程序代码的,便会引发错误,典型的控件便是TreeView!那这该如何解决呢?这得视欲载入的控件而定了。TreeView控件是很难从外部修改,令其可动态加载至UpdatePanel中的,不过我们的UserControl没那么复杂,只要运用ScriptManager控件的Scripts属性,加载指定的JavaScript程序文件即可。请照着下面的步骤做。
1. 创建一个新网页,命名为DynamicLoadUserControl2.aspx。
2. 在页面中放入一个ScriptManager控件。
3. 在页面中放入一个UpdatePanel控件。
4. 放一个Button控件至UpdatePanel控件中。
5. 在Button的Click事件中键入程序3-20的代码。
6. 创建一个UserControl,命名为WebUserControl1.ascx,内容如程序3-21所示。
7. 创建一个JavaScript文件,命名为JScript.js,内容如程序3-22所示。
8. 点击ScriptManager控件的Scripts属性,加入引用JScript.js文件,如图3-18所示。
程序3-20
Samples\3\DynamicLoadUserControl1.aspx protected void Button1_Click(object sender, EventArgs e) { if (UpdatePanel1.ContentTemplateContainer.FindControl("DynamicControl1") == null) { Control c = Page.LoadControl("WebUserControl1.ascx"); c.ID = "DynamicControl1"; UpdatePanel1.ContentTemplateContainer.Controls.Add(c); } }
程序3-21
Samples\3\AjaxDemo1\WebUserControl1.ascx <%@ Control Language="C#" AutoEventWireup="true" CodeFile= "WebUserControl1.ascx.cs" Inherits="WebUserControl1" %> </script> <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" OnClientClick="showMessage('Test')" />
程序3-22
function showMessage(str) { alert(str); } //很重要,所有通过ScriptReference并以Path方式加载的.js文件中都必须包含这段程序代码, //告诉ASP.NET AJAX这个.js已经完全加载完毕. if(Sys && Sys.Application) { Sys.Application.notifyScriptLoaded(); }
在程序3-22 的代码末行,调用了Sys.Application.notifyScriptLoaded函数,此举目的在于告诉ASP.NET AJAX的Script Loader这个JavaScript程序文件已经完全加载完毕。

图3-18
这里的Path指的是JScript.js的位置,若放于其他目录中,例如scripts目录中,即设为“scripts/JScript.js”,指定的文件将会于此网页初次绘制时便被载入,以本例来说,原本动态加载时是无法加载JavaScript的,但是只要这样设置后,该JavaScript早就已经被加载,所以本例便可运行了。除了载入真实的JavaScript文件外,ScriptManager控件也允许加载Assembly中的Script资源,这部分将于后面ASP.NET AJAX延展性一节中讨论。完成并运行本例后,便可发现其已能正常运行了,如图3-19所示。

图3-19
Scripts中指定的JavaScript文件的加载位置会受到ScriptManager之LoadScriptsBeforeUI属性值影响,默认此属性值为True,也就是于UI控件载入前载入这些Scripts,若此值为False,那么Scripts便会于UI控件加载后加载,这会造成什么影响呢?当此值为False时,Scripts载入于UI控件加载之后,因此若Script中有全局程序代码时,且这些程序代码中企图以$get函数取得UI控件的HTML元素对象时,将可以正常运行。反之若此值为True,加载Script时UI控件尚未完全加载,所以在全局程序代码中调用$get函数时便会回传NULL。举个较实际的例子,若将程序3-22改成如程序3-23,那么在LoadScriptsBeforeUI值为True时会产生错误,为False则不会。
程序3-23
function showMessage(str) { alert(str); } var p = $get('Button1').value; //for every script include at scriptReference with path of ScriptManager/ ScriptManagerProxy, //you must add below code to notify Script is loaded,or you may get Sys. ScriptLoadFailedException Exception;. //if your scriptReference is with Assembly,you can set NotifyScriptLoaded to true, //ScriptManager will add notifyScriptLoaded code for your script code. if(Sys && Sys.Application) { Sys.Application.notifyScriptLoaded(); }