zoukankan      html  css  js  c++  java
  • 控件开发:JavaScript的呈现逻辑和方法

          我们在写自定义控件的时候,避免不了不用JavaScript,而怎么在控件中使用并且高效的呈现到客户端,是我们要考虑的问题,所以我在下面结合一个简单的CusterHyperLink控件,来介绍我们的控件怎么去加载并呈现到客户端的过程。

    1.  首先我们要实现这个控件,大致要怎样的步骤呢?

    当我们开发这样的控件的时候,主要涉及到以下的内容:
         (1)    使用该控件,并封装实现Html和客户端脚本;
         (2)    确定如何将客户端脚本呈现包含在页面中(控件从Control WebControl继承,会有不同)
         (3)    控件应该包括一个确定合适呈现脚本的程序逻辑,不必总是呈现客户端脚本
         (4)    当所请求的浏览器不支持客户端能力的时候,允许控件退回执行服务器端代码

    2.呈现方式

       2.1 将脚本呈现为Html属性值
            这个比较简单,我们可以考虑这样一个例子,一个超级链接,鼠标移出或移上去,会有不同的背景。
            Html代码1如下:
             <a href="Http://xbf321.cnblogs.com" onmouseover="this.style.backgroundColor='Blue';this.style.color='yellow';this.style.fontSize='xx-large';this.style.fontWeight='bold';" onmouseout="this.style.backgroundColor='White';this.style.color='Blue';this.style.fontSize='small';this.style.fontWeight='normal';" style="background-color:Write;color:Blue;font-size:small;font-weight:normal;">点我</a>
          下面将开发一个自定义控件CustomHyperLinkAttr。该控件可自动完成上述两个步骤。
          因为我们只给链接加上属性,所以我们选择一个现成的.Net 控件HyperLink,作为这个控件的基类,我们重写此控件的AddAttributesToRender方法,该方法呈现包含Html元素的属性,代码2如下:
          我们在页面上,只要使用这个控件的就可以了,而不必写上一大堆的Html代码

    Code

    <form id="form1" runat="server"> <custom:CustomHyperLinkAttr runat="server" NavigateUrl="Http://xbf321.cnblogs.com" Text="点我"></custom:CustomHyperLinkAttr></form>,
          一定要在页面或Web.Config中引用以下哦,我想这是最基本的,不用我在详细的说了吧。呵呵,可以参考ClingingBoyhttp://www.cnblogs.com/Clingingboy/archive/2006/07/30/463471.html

       2.2 <script></script>中呈现脚本块
           在上面的代码1种,我们的链接包含Css样式属性和Js代码,如果我们的页面上有好多这样的链接,那我们的页面,就会非常的臃肿,维护也是个问题,所以我们把js部分提炼出来用JavaScript来对链接操作,并在元素<a>onmouseover onmouseout属性中调用这两个个函数即可
        
    代码3

    Code


              接下来,我们修改一下上面的那个自定义控件,我们在此控件的Render方法中把这些的JavaScript函数给输出来,而在AddAttributesToRender方法中修改onmouseoveronmouseout二个属性。
            代码4

    Code

     注意注释掉的部分
           如上代码所示,该控件重写了Render方法,以便将客户端脚本呈现为一个脚本快,同时还重写了AddAttributesRender方法,将其呈现为包含Html元素<a>onmouseoveronmouseout 的属性值。
           如果页面开发人员在同一个页面上使用两个或多个这样的控件,会发生什么呢?
           如你所想,页面上就会有多个像代码3这样的脚本快。
           这就意味着自定义控件的每个事例都会调用一次Render方法,所以就会在页面上重复这样的脚本块,那我们怎么才能避免这种问题呢?
           Page
    类提供了这样一种机制,其允许在包含页面的Render方法中注册并判断是否已经注册脚本块,代码5如下
            Public void RegisterClientScriptBlock(Type type,string key,string script,bool addTag)
           该方法中typekey共同形成关键字,并通过这个关键字来注册脚本快,在呈现的过程中,页面将检查所注册的脚本快内部列表中是否包括该关键字的脚本快,如果包括,则不会创建该脚本块,否则反之。最后的一个参数,让你决定自己呈现像<script>这样的标签,如果false,自己必须写<script>,反之,他们自动对脚本添加上<script></script>
           如果控件呈现的Html代码需要访问Javascript,那么就必须使用RegisterClientScriptBlock方法注册,因为只有呈现呈现在脚本快后面的Html才能访问该脚本,如果脚本需要访问Html那么只有使用RegisterStartupScript方法。这两个的参数都是一样的。可以举一反三明白其他的。
          
    在控件尝试注册脚本快之前,可以调用IsClientScriptBlockRegistered/IsClientStartScriptRegister方法来检测该脚本是否已经注册过,在相同的关键字下,对相同的客户端进行多次注册时允许的,这是因为该操作,并没有将相同脚本的多个副本添加到页面的注册脚本内部列表中。因此控件应该调用IsClientScriptBlockRegistered/IsClientStartScriptRegister方法。那么应该在什么时候调用该方法呢?
           如果控件必须花费大量的时间来生成已注册过的脚本,那么有可能会降低性能,在这种情况下,就首先调用IsClientScriptBlockRegistered/IsClientStartScriptRegister方法,以便脚本是没有经过注册的:
           Public bool IsClientScriptBlockRegisted(String key)
           在内部,该方法会调用以下方法的重写,并将包含页面的System.Type对象和key作为参数传递给该方法:
           Public void RegisterStartupScript(Type type ,string key,string script,bool addScriptTags)
           并和key形成关键字,来确保脚本是否已经注册。
            回顾一下代码4,该代码直接在Render方法中直接呈现控件的脚本快,上面我们说过,该控件不应该直接呈现脚本块,而是应该向包含页面注册脚本块,以便让页面自己呈现脚本块,以下是新版本的CustomHyperLinkAttr控件代码,该控件重写了OnPreRender方法,以便向页面注册客户端脚本代码5

    Code

    强烈要求读懂下面的文字:
          如以上所示,控件必须在生命周期的预呈现阶段注册脚本块,开发人员不应该在空间的呈现阶段中调用包含页面的ClientScript属性的注册方法(RegisterClientScriptBlock).
          当包含页面进入生命周期的呈现阶段,叶面将在控件树内递归调用每个控件的RenderControl方法,以便呈现每个控件,这意味着页面时在控件呈现之前开始呈现。因此,如果控件在呈现阶段调用注册方法,那么将在页面进入呈现阶段后注册客户端脚本(重要)
       2.3 将控件的脚本打包放在一个单独得js文件中,并在页面上引用作为<script>元素的src属性值

         回顾上面的事例,其中页面开发人员直接将脚本块加入页面,则会造成以下问题
         1,页面杂乱,不易维护
         2,每次更新时,还要确保没有破坏其他东西
         3,不允许浏览器缓存脚本,无法在使用相同脚本的页面重用脚本
         4,对控件本身维护也是个问题
         为了避免以上问题,所以通常把脚本打包为一个.js文件并在页面上引用这个js文件,这中方式具有以下优点
         1,分离Htmljs,那么页面开发人员在修改Js文件的同时,不影响Html
         2,允许浏览器缓存js文件,并且在不用的页面上使用它们
         3,由于页面没有包含脚本,所以减小页面大小
    代码6 所示

    <head runat="server">

        
    <title>Untitled Page</title>

        
    <script src="base.js" type="text/javascript"></script>

    </head>

    <body>

        
    <form id="form1" runat="server">

       

       
    <custom:CustomHyperLinkAttr runat="server" NavigateUrl="Http://xbf321.cnblogs.com" Text="点我"></custom:CustomHyperLinkAttr><br />

       
    <custom:CustomHyperLinkAttr ID="CustomHyperLinkAttr1" runat="server" NavigateUrl="Http://xbf321.cnblogs.com" Text="点我2"></custom:CustomHyperLinkAttr>

        
    </form>

          注意以上代码的深蓝色部分,页面没有额外的js脚本块,只有对外部js的引用。
            在代码5种,我们是在控件的OnPreRender中把脚本块用RegisterScriptBlock注册页面中,页面直接将客户端代码呈现在Html中,接着发送到请求的浏览器,但是这样是不对的。
           换句话说。在OnPreRender中,不应该把脚本直接注册到页面注册脚本内部列表,而应该使用脚本文件的Url,并生成客户端脚本包含,而后再用RegisterScriptBlock方法,如代码7所示
          代码7

    string strJs2 = "<script src=\"base.js\" type=\"text/javascript\"></script>";

                Type type 
    = typeof(CustomHyperLinkAttr);

                
    if (!Page.ClientScript.IsClientScriptBlockRegistered(type, type.FullName))

                
    {

                    Page.ClientScript.RegisterClientScriptBlock(type, type.FullName, strJs2);

                }


                
    base.OnPreRender(e);

            Page类的ClientScript属性包括一个RegisterClientScriptInclude方法,该方法封装了代码7种红色部分,从而简化自定义控件的OnPreRender方法,如代码8

    protected override void OnPreRender(EventArgs e)

            
    {Type type = typeof(CustomHyperLinkAttr);

                
    if(!Page.ClientScript.IsClientScriptIncludeRegistered(type,type.FullName))

                
    {

                    Page.ClientScript.RegisterClientScriptInclude(type.FullName, 
    "base.js");

                }


                
    base.OnPreRender(e);

            }


            注意:ClientScript中有一系列的方法都是配合来的比如:
            Public void RegisterXXX Public bool IsRegisterXXX,这些可以通过MSDN的资料查一下,我在这就不过多的详述了,赫赫

    3.部署

       正如以上所述,把脚本放到一个单独得脚本文件中,这样页面可以用页面包含的方式引用,但是这样也是有缺点的,比如必须把脚本部署到客户端,这就造成了自定义脚本部署的困难,特别是当控件2个不同的版本同时在客户端运行的时候。
       自定义控件的其他文件也存在同样的问题,如Css Image.Asp2.0提供了两种部署方案解救这个部署问题,一是将源文件部署到一个共享位置,另一个是将源文件嵌入到程序及中。
         3.1 将源文件部署到一个共享位置
               Asp.Net 1.1 中我们都只知道在IIS下有一个aspnet_client文件夹,其实这个文件夹就是提供各个Asp.Net程序公用的一个js文件夹,里面有根据版本的问题,设置了几个不同的文件夹,来共Asp.Net 选择,我们可以把js文件放到这个里面,以达到公用的目的,然而这种部署方式也可能导致错误,因为控件和其资源在不同的文件中,每次部署控件都必须确保相应得源文件也被部署,这就造成部署困难,所以,在这我强烈建议你不用这种方式,而选择第二种部署方式。
         3.2 将源文件嵌入程序集中
               用这种方式,不需要部署独立的源文件,由于控件的每个版本包含js文件,所以这又解决了版本问题。
               那如何使用这种部署方式呢?
               首先,我们需要下列两项操作
              1,把js的源文件添加到Vs的控件项目中
              2,把这个源文件属性的BuildAction属性,设置为Embedded Resource.这样可以通知Vs将源文件嵌入到程序集中

      一个文件嵌入到程序中,并不说明就应该使用该资源,我们必须把一下代码添加到AssemblyInfo.cs中。
            [assembly: WebResource("CustomComponents.base.js", "text/javascript")]
            WebResource 属性的第一个参数是资源名称,有命名控件和js文件名组成,第二个是MIME类型,如图片的MIME类型为image/jpeg一样。
          代码9 是我们用这种方式实现

    protected override void OnPreRender(EventArgs e)

            
    {

                Type type 
    = typeof(CustomHyperLinkAttr);

                
    string strUrl = Page.ClientScript.GetWebResourceUrl(type, "CustomComponents.base.js");

                
    if(!Page.ClientScript.IsClientScriptIncludeRegistered(type,type.FullName))

                
    {

                    Page.ClientScript.RegisterClientScriptInclude(type.FullName, strUrl);

                }


                
    base.OnPreRender(e);

            }


    注意以上红色部分,Page类的ClientScript属性包括一个RegisterClientScriptResource方法,该方法封装了代码9的红色部分,从而简化了自定义控件的OnPreRender方法,如代码10

    protected override void OnPreRender(EventArgs e)

            {

                Type type 
    = typeof(CustomHyperLinkAttr);

                Page.ClientScript.RegisterClientScriptResource(type,
    "CustomComponents.base.js");

                
    base.OnPreRender(e);

            }

       注意以上代码的不同之处

    4.呈现控件客户端脚本的时机
       以上介绍了控件呈现客户端脚本的不同方式,然而,控件不应该总是呈现客户端脚本,请求的浏览器可能不支持Ajax功能,如果对所有的浏览器都呈现脚本,那么控件可能不会再所有的浏览器上正常工作。
          同时,我们应该能关闭控件的客户端行为,而不考虑请求浏览器的Ajax功能。
          4.1 禁用控件的客户端行为
         如代码11所示,其包含一个bool属性,以便我们可以关闭控件的客户端行为,当该属性为false时,将不呈现客户端脚本

         

    /// <summary>

            
    /// 是否启用客户端脚本

            
    /// </summary>

            
    public bool EnableClientScript

            {

                
    get 

                { 

                    
    if(ViewState["EnableClientScript"!= null)

                    {

                        
    return (bool)ViewState["EnableClientScript"];

                    }

                    
    else

                    {

                        
    return true;

                    }

                }

                
    set

                {

                    ViewState[
    "EnableClientScript"= value; 

                }

            }

         4.2 检查客户端浏览器是否支持Ajax功能
            地定义控件必须包含逻辑,以便检查请求的浏览器Ajax功能是否满足控件需要,在没有显示禁用客户端脚本时,该逻辑才能运行。
           Request对象包括一个HttpBrowerCapabilities类型的Brower属性,我们可以用它来检测一下请求的浏览器是否支持Ajax功能。
          为确定呈现脚本时机,还必须包括一个私有Bool字段的_renderClientScript.以及一个受保护的虚拟方法DetermineRenderClientScript(或其他名称),该方法使用EnabelClientScript属性来检查页面开发人员是否禁用客户端行为,如果是,则_renderClientScriptfalse,并返回。
         如果不是,该方法则应用Request对象的Brower属性,来检查请求的浏览器Ajax功能是否符合要求。如果做了这项工作,该方法则将_renderClientScript字段设置为ture,并返回,反之。如代码12所示:

    private bool _renderClientScript = false;

            
    protected virtual void DetermineRenderClientScript()

            {

                
    if(!EnableClientScript)

                {

                    _renderClientScript 
    = false;

                    
    return;

                }

                System.Web.HttpBrowserCapabilities browser 
    = Page.Request.Browser;

                
    if(browser.MajorVersion >=4)

                {

                    _renderClientScript 
    = true;

                }

            }


                注意DetermineRenderClientScript方法不负责呈现脚本,他只设置_renderClientScript的值,也就是控件的呈现方法必须首先检查这个私有变量,以后,才能尝试呈现客户端脚本。
         由于控件的脚本在包含页面的生命周期的呈现阶段,所以控件必须重写OnPreRender方法来调用DetermineRenderClientScript方法,从而设置_renderClientScript私有字段的值,只有才能进入呈现阶段。如代码13所示:             

    protected override void OnPreRender(EventArgs e)

            {

                DetermineRenderClientScript();

                
    if(_renderClientScript)

                {

                    Type type 
    = typeof(CustomHyperLinkAttr);

                    Page.ClientScript.RegisterClientScriptResource(type, 
    "CustomComponents.base.js");

                }

                

                
    base.OnPreRender(e);

            }

       注: 在读THIN <<控件开发-道不远人>>一书的时候,里面也有类此的代码,当初知道其意思是检测是否支持脚本,但是就是不明白其为什么要这样写,到这我才知道其原理。。赫赫

    5.呈现控件客户端脚本的位置
        根据控件的客户端脚本的呈现方式,可以重写下列方法中的一个或者多个来呈现脚本:

       Void OnPreRender()

       Void AddAttributesToRender()

       Void RenderContents()

       Void Render()

    选择重写那个方法取决于客户端脚本类型(由控件呈现岛包含页面中的客户端脚本),下面将对每个方法进行讨论:

    5.1 重写OnRreRender

       如果要呈现到页面为一个脚本块(位于<script></script>之间的内容),那么控件必须重写OnPreRender方法以便调用包含页面ClientScript属性的合适方法,请求页面才能为控件呈现该脚本。

    5.2 重写AddAttributesToRender方法

       如果客户端脚本,要作为控件内部元素的属性值来呈现比如<a>标记的OnMouseOver属性,控件必须重写此方法。

    5.3 重写RenderContents方法

       如果客户端脚本(该脚本需要由控件呈现到包含页面中)在控件的包含元素的开始和结束标记内呈现,那么控件必须重写此方法,此方法,还必须检查_renderClientScript字段的值,以监测是否应该呈现该脚本。

    5.4 重写Render方法

      如果控件不是直接或者间接继承自WebControl。或者继承自WebControl但仍然需要重写磁方法,那么控件应该重写Render方法,来呈现该脚本。一般情况下,如果控件直接或间接继承自WebControl ,控件不应该重写Render方法,相反,应该重写AddAttributesToRender方法。

    6.退回到服务器端代码

       如上所述,开发人员在禁用控件的客户端功能的情况下,控件必须能退回到完全有服务器实现,并不能因为为此,而让控件产生错误(如:弹出Js错误),想上面的CustomerHyperLink控件,如果禁用客户端功能,该控件就应该成为普通的HyperLink控件,并继续正常工作。

        希望这篇文章能够对在控件开发中,不知道怎么呈现Js的用户提供帮助。

    参考资料:Asp.Net 2.0 Server Control And Component Development

  • 相关阅读:
    发呆发呆发呆发呆发呆发呆发
    大众捷达看想吃 v 觉得分开才相聚离开都出现
    yjggj
    test4
    test3
    test2
    test1
    Java并发之线程池ThreadPoolExecutor源码分析学习
    Java并发之AQS同步器学习
    ThreadLocal和ThreadLocalMap源码分析
  • 原文地址:https://www.cnblogs.com/xbf321/p/Render_JavaScript_In_Server_Control.html
Copyright © 2011-2022 走看看