65.9K
CodeProject 正在变化。 阅读更多。
Home

虚拟表单 Web 自定义控件

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2009年1月23日

CPOL

6分钟阅读

viewsIcon

21258

downloadIcon

185

您是否曾想过,“要是有一个控件——就像面板控件一样——您只需用它来包装一些输入控件,设置一个简单的属性(指向当按下 Enter 键时应该被“点击”的控件的 ID),这就足够了。” 那么,现在有一个这样的控件了。

引言

我最近遇到一个问题,我开发的一个网站的页面顶部有表单字段(用于登录或搜索),并且当我将光标放在页面下方的一个表单字段中时,如果我按下 Enter 键,触发的是页面上第一个按钮的点击事件(顶部栏中的按钮,而不是我希望它使用的按钮)。这种设置很常见:母版页包含一个带有 `runat="server"` 属性的 `form` 标签,并且这个 `form` 包裹了页面的全部内容。这一切似乎都运行正常,直到我遇到注册页面,当我想点击“立即注册”按钮时,我只是按了 Enter。结果没有注册我,而是出现了一个消息,解释说用户名和密码是必需的。由于这些字段也存在于注册表单上,我感到很困惑,更何况当我用鼠标点击“立即注册”按钮时,一切都正常。通过代码调试,我很快就意识到发生了什么,并翻找了我几年前写的一些 JavaScript 代码,这些代码可以监听 Enter 键,取消默认行为,并调用另一个方法。我将这段代码添加到页面,一切都好了,直到我发现另一个页面有同样的问题。就在那时我想,“要是有一个控件——就像面板控件一样——您只需用它来包装一些输入控件,设置一个简单的属性(指向当按下 Enter 键时应该被“点击”的控件的 ID),这就足够了。”

现在,有一个这样的控件了。

编辑:实际上,正如 Onskee1 在下面的评论中指出的那样,标准的 ASP `Panel` 控件有一个“`DefaultButton`”属性,它实现了类似的功能;但是,它只允许您使用 ASP `Button` 控件作为指定的按钮。如果您想使用 ASP `LinkButton` 控件或任何其他类型的控件作为默认按钮,它对您没有任何作用。所以,如果您只使用 ASP `Button` 控件,我建议您使用该属性。否则,请继续阅读……

使用代码

您可以根据需要逐页注册控件,或者将其添加到 `web.config` 文件的 `<Pages>` 部分,使其可供站点上的任何页面使用(列表 1)。

列表 1
<pages enableeventvalidation="true" 
           enableviewstatemac="true" enablesessionstate="true">
      <controls>
        <add assembly="WilliaBlog.Net.Examples" 
            namespace="WilliaBlog.Net.Examples" tagprefix="WBN">
      </add>
  </controls>
</pages> 

然后,以将控件包装您的输入的方式将其添加到页面中

列表 2
<WBN:VirtualForm id="vf1" runat="server" SubmitButtonId="Button1" UseDebug="false"> 
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
    <asp:Button ID="Button1" runat="server" Text="Button1" OnClick="Button1_Click" /> 
</WBN:VirtualForm>

从列表 2 可以看出,您应该使用按钮的服务器端 ID(虚拟表单控件会自动将其转换为客户端 ID)。在线测试

工作原理

实际的服务器端虚拟表单 Web 自定义控件非常简单。它继承自 `System.Web.UI.WebControls.Panel`,并包含一个属性,允许您设置按下 Enter 键时要推送的 `Button` 或 `LinkButton` 的 ID。为了方便移植,我选择将 JavaScript 文件嵌入到 DLL 中。(最初,我将其放在一个外部 `.JS` 文件中,并添加到母版页的头部,但是如果我——或者其他人——想在另一个站点上使用这个控件,这将增加额外的设置复杂性——他们必须记住加载那个 JavaScript 文件,并且虽然我们可以将它托管在一个中心位置供所有站点共享,但将文件嵌入 DLL 似乎是明智的选择。)这种技术的缺点是,每次请求页面时,`js` 文件都必须通过网络传输,因此无法在客户端缓存,这会对 Web 应用程序的低带宽用户产生负面影响。因此,我使用了 YUI Compressor 来删除所有注释和额外的空格,并在 DLL 中包含了两个版本的脚本。当您将 `UseDebug` 属性设置为 true 时,它将使用冗长的版本,这在 Firebug 中更容易调试,但是当一切正常工作时,通过从控件声明中省略此属性或将其设置为 false 来使用压缩版本。

要使文件可从您的服务器控件的程序集中访问,只需将文件添加到项目中,转到“属性”窗格,然后将“生成操作”设置为“嵌入的资源”。要将文件暴露给 Web 请求,您需要添加列表 3 中第 7 行和第 8 行这样的代码。这些条目将嵌入的资源暴露出来,以便 `ClientScriptManager` 既可以访问文件,又能知道它是什么类型的文件。您也可以通过这种方式嵌入 CSS、图像和其他类型的文件。 **注意**:项目默认命名空间(在项目属性页的“应用程序”选项卡中定义)需要作为文件名的前缀添加到嵌入式资源中。

因此,除了暴露这些属性之外,控件真正做的就是覆盖 `onPreRender` 事件并将一些 JavaScript 注入到页面中。列表 3 中第 75 到 79 行注入了对嵌入式 JavaScript 文件的链接,它在页面源代码中显示为类似这样:

<script src="/WebResource.axd?d=j246orv_38DeKtGbza6y6A2&t=633439907968639849" 
        type="text/javascript"></script>

接下来,它动态生成一个脚本来创建一个虚拟表单对象的实例,传递 `VirtualForm` 服务器控件的客户端 ID 和我们希望它使用的按钮的客户端 ID,并将此注册为页面上的启动脚本。

列表 3
1 using System; 
    2 using System.Collections.Generic; 
    3 using System.ComponentModel; 
    4 using System.Text; 
    5 using System.Web; 
    6 using System.Web.UI; 
    7 using System.Web.UI.WebControls; 
    8  
    9 // Script Resources 
   10 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_Debug.js", 
                             "text/javascript")] 
   11 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_min.js", 
                             "text/javascript")] 
   12  
   13 namespace WilliaBlog.Net.Examples 
   14 { 
   15  
   16     [ToolboxData("<{0}:VirtualForm runat=server></{0}:VirtualForm>")] 
   17     public class VirtualForm : System.Web.UI.WebControls.Panel 
   18     { 
   19         [Bindable(true), DefaultValue("")] 
   20         public string SubmitButtonId 
   21         { 
   22             get 
   23             { 
   24                 string s = (string)ViewState["SubmitButtonId"]; 
   25                 if (s == null) 
   26                 { 
   27                     return string.Empty; 
   28                 } 
   29                 else 
   30                 { 
   31                     return s; 
   32                 } 
   33             } 
   34             set { ViewState["SubmitButtonId"] = value; } 
   35         } 
   36  
   37         [DefaultValue(false)] 
   38         public bool UseDebug 
   39         { 
   40             get 
   41             { 
   42                 string s = (string)ViewState["UseDebug"]; 
   43                 if (string.IsNullOrEmpty(s)) 
   44                 { 
   45                     return false; 
   46                 } 
   47                 else 
   48                 { 
   49                     return s.ToLower() == "true"; 
   50                 } 
   51             } 
   52             set { ViewState["UseDebug"] = value; } 
   53         } 
   54  
   55         public VirtualForm() : base() { } 
   56  
   57         protected override void OnPreRender(System.EventArgs e) 
   58         { 
   59             if (!string.IsNullOrEmpty(this.SubmitButtonId)) 
   60             { 
   61                 Control theButton = this.FindControl(this.SubmitButtonId); 
   62                 if ((theButton != null)) 
   63                 { 
   64                   string resourceName; 
   65                   if (this.UseDebug) 
   66                   { 
   67                     resourceName = 
                             "WilliaBlog.Net.Examples.VirtualForm_Debug.js"; 
   68                   } 
   69                   else 
   70                   { 
   71                     resourceName = "WilliaBlog.Net.Examples.VirtualForm_min.js"; 
   72                   } 
   73  
   74                   ClientScriptManager cs = this.Page.ClientScript; 
   75  
   76                   string scriptLocation = 
                               cs.GetWebResourceUrl(this.GetType(), resourceName); 
   77                   if (!cs.IsClientScriptIncludeRegistered("VirtualFormScript")) 
   78                   { 
   79                       cs.RegisterClientScriptInclude("VirtualFormScript", 
                                                           scriptLocation); 
   80                   } 
   81  
   82                   // New script checks for "Sys" Object, if found
                        // events will be rewired after updatepanel refresh. 
   83                   StringBuilder sbScript = new StringBuilder(333); 
   84                   sbScript.AppendFormat("<script type=\"text/javascript\">{0}", 
                                              Environment.NewLine); 
   85                   sbScript.AppendFormat("    // Ensure postback works after " + 
                                              "update panel returns{0}", 
                                              Environment.NewLine); 
   86                   sbScript.AppendFormat("    function " + 
                                              "ResetEventsForMoreInfoForm() {{{0}", 
                                              Environment.NewLine); 
   87                   sbScript.AppendFormat("        var vf_{0} = new WilliaBlog.Net." + 
                                              "Examples.VirtualForm(" + 
                                              "document.getElementById" + 
                                              "('{0}'),'{1}');{2}", this.ClientID, 
                                              theButton.ClientID, Environment.NewLine); 
   88                   sbScript.AppendFormat("    }}{0}", Environment.NewLine); 
   89                   sbScript.AppendFormat("    if (typeof(Sys) !== \"undefined\"){{{0}", 
                                              Environment.NewLine); 
   90                   sbScript.AppendFormat("        Sys.WebForms.PageRequestManager." + 
                                              "getInstance().add_endRequest(" + 
                                              "ResetEventsForMoreInfoForm);{0}", 
                                              Environment.NewLine); 
   91                   sbScript.AppendFormat("    }}{0}", Environment.NewLine); 
   92                   sbScript.AppendFormat("    var vf_{0} = new WilliaBlog.Net." + 
                          "Examples.VirtualForm(document.getElementById('{0}'),'{1}');{2}", 
                          this.ClientID, theButton.ClientID, Environment.NewLine); 
   93                   sbScript.AppendFormat("</script>"); 
   94  
   95                   string scriptKey = string.Format("initVirtualForm_" + 
                                                         this.ClientID); 
   96  
   97                   if (!cs.IsStartupScriptRegistered(scriptKey)) 
   98                   { 
   99                       cs.RegisterStartupScript(this.GetType(), scriptKey, 
                                              sbScript.ToString(), false); 
  100                   } 
  101                 } 
  102             } 
  103             base.OnPreRender(e); 
  104         } 
  105     } 
  106 }

JavaScript

当然,大部分代码是 JavaScript。第 13 到 62 行只是创建了 `WilliaBlog` 命名空间,我从 Yahoo! 用户界面库 (YUI) 中“借用”了它。`WilliaBlog.Net.Examples.VirtualForm` 对象从第 65 行开始。本质上,它遍历父 `Div`(此 `div` 的 ID 作为参数传递给构造函数)中的每个输入控件(第 159-164 行),并为它们中的每一个分配一个 `onkeypress` 事件(`handleEnterKey`)。除了 Enter 键之外的所有按键都会透明地通过代码,但一旦检测到 Enter 键,就会取消默认行为,而是调用 `submitVirtual` 函数。该函数只是检查您提供的按钮是输入(图像或提交按钮)还是链接(超链接或链接按钮),并通过调用前者或后者的 `click()` 方法或导航到后者的 `href` 属性来模拟点击。`removeEvent` 和 `stopEvent` 方法实际上从未被调用,但我为了以防万一包含了它们。

列表 4
    1 /*********************************************************************
    2 * 
    3 * File    : VirtualForm_Debug.js 
    4 * Created : April 08 
    5 * Author  : Rob Williams 
    6 * Purpose : This is the fully annotated, easy to understand
                  and modify version of the file, 
                  however I would recommend you use 
    7 * something like the YUI Compressor 
       (http://developer.yahoo.com/yui/compressor/) to minimize load time. 
    8 * This file has its Build Action Property set to "Embedded Resource" 
        which embeds the file inside the dll, so we never have to 
    9 * worry about correctly mapping a path to it. 
   10 * 
   11 **********************************************************************/ 
   12  
   13 if (typeof WilliaBlog == "undefined" || !WilliaBlog) { 
   14     /** 
   15     * The WilliaBlog global namespace object.
          * If WilliaBlog is already defined, the 
   16     * existing WilliaBlog object will not be overwritten so that defined 
   17     * namespaces are preserved. 
   18     * @class WilliaBlog 
   19     * @static 
   20     */ 
   21     var WilliaBlog = {}; 
   22 } 
   23  
   24 /** 
   25  * Returns the namespace specified and creates it if it doesn't exist 
   26  * <pre> 
   27  * WilliaBlog.namespace("property.package"); 
   28  * WilliaBlog.namespace("WilliaBlog.property.package"); 
   29  * </pre> 
   30  * Either of the above would create WilliaBlog.property, then 
   31  * WilliaBlog.property.package 
   32  * 
   33  * Be careful when naming packages. Reserved words may work in some browsers 
   34  * and not others. For instance, the following will fail in Safari: 
   35  * <pre> 
   36  * WilliaBlog.namespace("really.long.nested.namespace"); 
   37  * </pre> 
   38  * This fails because "long" is a future reserved word in ECMAScript 
   39  * 
   40  * @method namespace 
   41  * @static 
   42  * @param  {String*} arguments 1-n namespaces to create 
   43  * @return {Object}  A reference to the last namespace object created 
   44  */ 
   45 WilliaBlog.RegisterNamespace = function() { 
   46     var a=arguments, o=null, i, j, d; 
   47     for (i=0; i<a.length; i=i+1) { 
   48         d=a[i].split("."); 
   49         o=WilliaBlog; 
   50  
   51         // WilliaBlog is implied, so it is ignored if it is included 
   52         for (j=(d[0] == "WilliaBlog") ? 1 : 0; j<d.length; j=j+1) { 
   53             o[d[j]]=o[d[j]] || {}; 
   54             o=o[d[j]]; 
   55         } 
   56     } 
   57  
   58     return o; 
   59 }; 
   60  
   61 //declare the 'WilliaBlog.Net.Examples' Namespace 
   62 WilliaBlog.RegisterNamespace("WilliaBlog.Net.Examples"); 
   63  
   64 //declare Virtual Form Object 
   65 WilliaBlog.Net.Examples.VirtualForm = function(formDiv,submitBtnId) 
   66 { 
   67     this.formDiv = formDiv; //The id of the div that represents our Virtual Form 
   68     this.submitBtnId = submitBtnId;
          //The id of the button or Linkbutton that should be clicked when pushing Enter 
   69  
   70     // When using these functions as event delegates the
          // this keyword no longer points to this object as it is out of context 
   71     // so instead, create an alias and call that instead. 
   72     var me = this; 
   73  
   74     this.submitVirtual = function() 
   75     { 
   76         var target = document.getElementById(me.submitBtnId); 
   77         //check the type of the target: If a button then call the click method. 
   78         if(target.tagName.toLowerCase() === 'input') 
   79         { 
   80             document.getElementById(me.submitBtnId).click(); 
   81         } 
   82         //If a link button then simulate a click. 
   83         if(target.tagName === 'A') 
   84         { 
   85             window.location.href = target.href; 
   86         } 
   87     }; 
   88  
   89     this.handleEnterKey = function(event){  
   90         var moz = window.Event ? true : false; 
   91         if (moz) { 
   92             return me.MozillaEventHandler_KeyDown(event); 
   93         } else { 
   94             return me.MicrosoftEventHandler_KeyDown(); 
   95         } 
   96     }; 
   97  
   98     //Mozilla handler (also Handles Safari) 
   99     this.MozillaEventHandler_KeyDown = function(e) 
  100     { 
  101         if (e.which == 13) { 
  102             e.returnValue = false; 
  103             e.cancel = true; 
  104             e.preventDefault();
                  // call the delegate function that
                  // simulates the correct button click             
  105             me.submitVirtual();
  106             return false;        
  107         } 
  108         return true; 
  109     }; 
  110  
  111     //IE Handler 
  112     this.MicrosoftEventHandler_KeyDown = function() 
  113     { 
  114         if (event.keyCode == 13) { 
  115             event.returnValue = false; 
  116             event.cancel = true; 
                  // call the delegate function that simulates
                  // the correct button click 
  117             me.submitVirtual();
  118             return false; 
  119         } 
  120         return true; 
  121     }; 
  122  
  123     this.addEvent = function(ctl, eventType, eventFunction) 
  124     { 
  125         if (ctl.attachEvent){ 
  126             ctl.attachEvent("on" + eventType, eventFunction); 
  127         }else if (ctl.addEventListener){ 
  128             ctl.addEventListener(eventType, eventFunction, false); 
  129         }else{ 
  130             ctl["on" + eventType] = eventFunction; 
  131         } 
  132     }; 
  133  
  134     this.removeEvent = function(ctl, eventType, eventFunction) 
  135     { 
  136         if (ctl.detachEvent){ 
  137             ctl.detachEvent("on" + eventType, eventFunction); 
  138         }else if (ctl.removeEventListener){ 
  139             ctl.removeEventListener(eventType, eventFunction, false); 
  140         }else{ 
  141             ctl["on" + eventType] = function(){}; 
  142         } 
  143     }; 
  144  
  145     this.stopEvent = function(e) 
  146     { 
  147         if (e.stopPropagation){ 
  148         // for DOM-friendly browsers 
  149             e.stopPropagation(); 
  150             e.preventDefault(); 
  151         }else{ 
  152         // For IE 
  153             e.returnValue = false; 
  154             e.cancelBubble = true; 
  155         } 
  156     }; 
  157  
  158     //Grab all input elements within virtual form (contents of a div with divID) 
  159     this.inputs = this.formDiv.getElementsByTagName("input"); 
  160  
  161     //loop through them and add the keypress event
          //to each to listen for the enter key 
  162     for (var i = 0; i < this.inputs.length; i++){ 
  163         this.addEvent(this.inputs[i],"keypress",this.handleEnterKey); 
  164     } 
  165 }

历史

  • v1.0。
© . All rights reserved.