JavaScriptBuilder: 自定义控件的JavaScript处理程序类

分享于 

25分钟阅读

Web开发

  繁體 雙語

注释:

  • DLL/代码需要 JavaScriptBuilder DLL。
  • has控件可以添加到( 并从)的VS.NET 工具栏,但没有相关的icon。
  • 如果dll包含在应用程序文件夹的/bin 中,则可以将示例页放入任何Web窗体项目中。
  • dll的上面 已经使用. NET 框架v1.1生成,但是代码应该在v1.0中工作

介绍

当你开始在 ASP.NET 中编写 WebControl 或者 UserControl s 时,你将很快发现需要使用容器页面的register 片段。

使用作为页面对象的一部分包含的API相当简单,特别是:

IsClientScriptBlockRegistered已经注册了一个给定的ClientScript?
RegisterClientScriptBlockregister 一块 ClientScript
IsStartupScriptRegistered已经注册了一个给定的StartupScript?
RegisterStartupScriptregister 一块 StartupScript。
RegisterArrayDeclarationregister 全局 array 中的元素,脚本可以访问它。
RegisterHiddenFieldregister 在 JavaScript ( 对 postback 数据有用) 中使用的隐藏字段。
RegisterOnSubmitStatement注册要在表单的onsubmit 事件中执行的一些 JavaScript。

本文的目的不是单独或者详细地覆盖这些文章,可以能是在另一篇文章中完成的。

本文和可下载类库旨在解决 RegisterClientScriptBlockRegisterStartupScriptRegisterOnSubmitStatement 常见的特定问题,并提供一个非常简单的解决方案。

作为一个整体主题,问题涉及到维护 vs 效率的旧的开发问题。 这篇文章试图在一个既不鼓励。

问题 1: 字符串处理

每个 上面 方法都以 string 格式接受一段JavaScript代码作为参数。 但是使用 ASP.NET string 对象构建字符串可能会浪费资源。 考虑三个最佳选项:

  • 简单地使用 + 操作符甚至 String.Concat 在构建整个JavaScript代码Fragment时都会为每个连接创建一个新的字符串,使用大量。
  • String.Format 或者 StringBuilder.AppendFormat 可以能是强大且更高效的,但是在 C# 中使JavaScript代码( 或者 VB.NET) 更少可以读)。 在打开/关闭代码块("{"/"}") against格式化位置标记("{0}") 时出现问题。
  • StringBuilder.Append的重复调用是最有效的选择,但这可能导致难以管理的代码。

使用这个简单的JavaScript,例如( 我们将在文章中回顾这一点。):

<script language="javascript">
<!--function MyWebControl_AlertText ()
{
 alert("MyWebControl");
}// --></script>

脚本只会抛出一个包含 WebControl的NAME的消息框。 它将被控件注册,以避免多次发生,但是函数 NAME 和文本文本必须由继承者重写。

因此,要使用选项 1 ( string + string ) 创建这里代码,你需要编写以下代码:

string script = "<script language="""javascript">" + Environment.NewLine
 + "<!--" + Environment.NewLine
 + "function" + FunctionName + " ()" + Environment.NewLine
 + "{" + Environment.NewLine
 + "talert(" + ControlName +");" + Environment.NewLine
 +"}" + Environment.NewLine
 +"// -->" + Environment.NewLine + "</script>";

这是非常可以读的,但CLR将创建多达 19个字符串对象,直到垃圾收集器围绕它们。 在一个繁忙的网站上,这可能是一个严重的问题。

让我们看一下选项 2 ( 通过 StringBuilder 格式化字符串)。

StringBuilder sb = new StringBuilder();
sb.AppendFormat("<script language="""javascript">{0}", Environment.NewLine);
sb.AppendFormat("<!--{0}", Environment.NewLine);
sb.AppendFormat("function {0} (){1}", FunctionName, Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.AppendFormat("talert("{0}");{1}", ControlName, Environment.NewLine);
sb.Append("}");
sb.Append(Environment.NewLine);
sb.AppendFormat("//-->{0}", Environment.NewLine);
sb.Append("</script>");string script = sb.ToString();

这种方式格式化字符串并不是一个错误的选择,它当然是它的他两者之间的。 尽管字符串放在应该是的位置,但它仍然可以读;它比串联字符串更有效,但是它不是对 Append 进行重复调用。

那么让我们看一下第三个和 final 选项:

StringBuilder sb = new StringBuilder();
sb.Append("<script language="""javascript">");
sb.Append(Environment.NewLine);
sb.Append("<!--");
sb.Append(Environment.NewLine);
sb.Append("function");
sb.Append(FunctionName);
sb.Append(" ()");
sb.Append(Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.Append("talert("");
sb.Append(ControlName);
sb.Append("");");
sb.Append(Environment.NewLine);
sb.Append("}");
sb.Append(Environment.NewLine);
sb.Append("//-->");
sb.Append(Environment.NewLine);
sb.Append("</script>");string script = sb.ToString();

这绝对是一个销售服务器控件的理想选择,使用 StringBuilder 最高效率,但是只需要一段时间。 这不是一个漂亮的图像。

问题 2: 不同的样式

在所有的上面 样式中,我几乎可以选择选项 2. 你可以能完全选择另一个,因为这是非常可以接受的,但不适合我的思考方式。

同样,你可以能选择不使用 Environment.NewLine,更喜欢 rn,甚至只是 n ( 大多数浏览器都不关心),因为它的简单性和效率。 就个人而言,我宁愿在我的头上钉钉子,而不是处理。 即使我用来进行缩进的标签也可以在一段时间后在眼睛上痛痛。

有许多它的他的方法可以改进或者降级 上面 代码,具体取决于你的视角。 同样,它降低了维护 vs 效率。

你所做的每一个决定都会影响到你在后面的( 而且在工作环境中,这几乎会发生,不管我们想要假设它们都不会有多少。) 中选择你的代码。 这种效果可能是好的或者坏的,在开发控件时,你不能知道。

问题 3: 注释和空白

JavaScript开发总是让你遇到空白和 inline 注释的困境。

两者都是对带宽的浪费,甚至很少被看到。 有时你甚至可能不希望最终用户看到你在JavaScript中所使用的注释,甚至不需要付出大量努力就可以阅读代码。

另一方面,如果你选择不使用它们,那么读取你自己的代码将是一个令人。

在希望将服务器控件销售到web空间服务提供商的情况下,所有这些特别相关。

三个问题的简单解决

我们真正需要的是一个类,它封装了 StringBuilder,为了提高效率,但是我们不同的选项。

我们还需要一个类来限制我们的选项,并且对于任何开发人员( 甚至一个不熟悉这个类的人) 来说,我们都是。 如果我们可以复制所有早期选项的最佳特性,那么这将消除不同样式的JavaScript构建。

我们需要一个类,我们可以很容易地从易于管理的代码中提取出高效的代码( 就像我们在编译软件的调试版本和发布版本时一样)。

如果我们只能在这个类中实现一小部分调试,因为JavaScript很难调试,这不是很好。

因此,输入 JavaScriptBuilder 类。

步骤 1: 创建类

using System;using System.Text;namespace CP.WebControls
{
 publicclass JavaScriptBuilder
 {
 private StringBuilder sb = new StringBuilder();
 privateint currIndent = 0;
 privateint openBlocks = 0;
 privatebool format = false;
 public JavaScriptBuilder()
 {
 }
 public JavaScriptBuilder(bool Formatted)
 {
 format = Formatted;
 }// Script handling code here }
}

这个类的基本结构没有什么异常。

除了 System 之外,我们唯一需要的.NET 框架命名空间是 System.Text,对于 StringBuilder 类。

我们需要一个 StringBuilder 对象,当 JavaScriptBuilder 被实例化时创建。 它的余的private 成员处理格式化,以及是否会有。 这个后一个问题必须在我们创建对象时回答,因为它是在整个过程中使用的。

步骤 2: 添加行并检索结果

publicvoid AddLine(params string[] parts)
{
 // Append parts of the line to StringBuilder individually// - much more efficient than sb.AppendFormatforeach (string part in parts)
 sb.Append(part);
 // Add a new line sb.Append(Environment.NewLine);
}
publicoverridestring ToString()
{
 // Add the <script> tags and some comment blocks, so that// browsers that don't support scripts will not crash horriblyreturnString.Format(
 "<script language="""javascript">{0}<!--{0}{1}{0}//-->{0}</script>", 
 Environment.NewLine, 
 sb
 );
}

这里的唯一技巧是从我们的第一个问题定义中学习尽可能多的教训。

我们真正想要的是字符串连接的能力和可读性,以及 StringBuilder.Append()的效率。 使用 C# params 关键词我们可以这样做。

对于 ToString() 函数中的每一个 JavaScript,我们也可以添加开放和关闭序列,因为它们在我们所使用的脚本的每个 block 中都应该是相同的。

单独使用这两个代码,我们可以使用下面的代码来生成初始简单脚本 block:

JavaScriptBuilder jsb = new JavaScriptBuilder(true);
jsb.AddLine("function", FunctionName, " ()");
jsb.AddLine("{");
jsb.AddLine("talert("", ControlName, "");");
jsb.AddLine("}");string script = jsb.ToString();

比我们最初的三个选项更简单和更易读,并且我们的最佳案例选项没有任何效率。

步骤 3: 跟踪缩进

publicint Indent
{
 get { return currIndent; }
 set { currIndent = value; }
}
publicvoid OpenBlock()
{
 AddLine("{");
 currIndent++;
 openBlocks++;
}
publicvoid CloseBlock()
{
 // Check that there is at least one block openif (openBlocks <1)
 thrownew InvalidOperationException(
 "JavaScriptBuilder.CloseBlock() called when no blocks open" );
 currIndent--;
 openBlocks--;
 AddLine("}");
}
publicvoid AddLine(params string[] parts)
{
 // Open line with tabsfor (int i=0; i < currIndent; i++)
 sb.Append("t");
 // Append parts of the line to StringBuilder individually// - much more efficient than sb.AppendFormatforeach (string part in parts)
 sb.Append(part);
 // Add a new line sb.Append(Environment.NewLine);
}

这个代码允许开发人员使用 JavaScriptBuilder 类打开和关闭块,不必记住在任何给定时间打开它们自己的选项卡。 这也意味着你不必处理每一行开始处的转义码,这对我来说是一个重要的好处。

另一个不太明显的优点是可以将一段代码从块移动到外部( 反之亦然),而不用担心调整每个行的选项卡数量。

更加灵敏的读者可能会问自己为什么需要跟踪缩进和打开块的数量。 例如,在不打开新 block的情况下,可以能需要缩进一段代码,例如一行代码。

所以我们需要向调用程序公开 Indent 属性,而不破坏我们在 CloseBlock() 中使用的验证行。 在 ToString() 中也可以包含类似的验证,这样调用程序只能在所有代码块关闭后才将脚本转换为 string。 但是,我们不会注意调用程序是否已经有了缩进,因为它不会使脚本失败。

注意 openBlocks 不是以任何方式公开的,它是完全在内部处理的。

再次回到先前简单的脚本中,它现在可以像下面这样轻松编写:

JavaScriptBuilder jsb = new JavaScriptBuilder(true);
jsb.AddLine("function", FunctionName, " ()");
jsb.OpenBlock();
jsb.AddLine("alert("", ControlName, "");");
jsb.CloseBlock();string script = jsb.ToString();

步骤 4: 处理格式标志

publicvoid AddLine(params string[] parts)
{
 // Open line with tabs, where formatting is setif (format)
 for (int i=0; i < currIndent; i++)
 sb.Append("t");
 // Append parts of the line to StringBuilder individually// - much more efficient than sb.AppendFormatforeach (string part in parts)
 sb.Append(part);
 // Append a new line where formatting is set or a space// where it isn'tif (format)
 sb.Append(Environment.NewLine);
 elseif (parts.Length >0)
 sb.Append("");
}
publicvoid AddCommentLine(params string[] CommentText)
{
 if (format)
 {
 // Open the line with tab indentfor (int i=0; i < currIndent; i++)
 sb.Append("t");
 //.. . and a comment marker sb.Append ("//");
 // Append all the parts of the lineforeach (string part in CommentText)
 sb.Append(part);
 // Throw in a new line sb.Append(Environment.NewLine);
 }
}

AddLine()的更改意味着选项卡和新行将只在设置 format 标志的位置被添加。 如果没有设置标志,那么我们只需用一个空格分隔每一行代码。 还有一个新方法 AddCommentLine(),它允许我们在设置 format 标志时添加注释。

演示一下,让我们向简单脚本 block 添加一个注释:

JavaScriptBuilder jsb = new JavaScriptBuilder(true);
jsb.AddLine("function", FunctionName, " ()");
jsb.OpenBlock();
jsb.AddCommentLine("A message box showing the Control name");
jsb.AddLine("alert("", ControlName, "");");
jsb.CloseBlock();string script = jsb.ToString();

当前,这里 JavaScriptBuilder 将生成以下脚本 block:

<script language="javascript">
<!--function MyWebControl_AlertText ()
{
 // A message box showing the Control name alert("MyWebControl");
}// --></script>

但如果你改变了 truefalse ( 或者允许它默认) 在构造函数中,脚本 block 未格式化如下:

<script language="javascript">
<!--function MyWebControl_AlertText () { alert("MyWebControl"); }// --></script>

这将在使用控件的每个页面上节省几个字节的带宽。 但是,如果一个单词更改为代码,则可以将脚本返回到原始状态,如果需要编辑或者调试它。

处理这里问题的一种简单方法是编写构造函数,如下所示:

#if DEBUGJavaScriptBuilder jsb = new JavaScriptBuilder(true);#elseJavaScriptBuilder jsb = new JavaScriptBuilder();#endif

这样我们就可以使用整齐。可以读的JavaScript来测试控件,而且在代码中没有一行更改。

确定是使用显式高度还是测量高度的一种简便方法

为了充分展示 JavaScriptBuilder的威力,我们应该创建一个具有更复杂脚本 block的控件。

ClickCounter 控件将显示一些文本以及单击计数器。 每次单击控件时,都会增加计数器并保留初始文本。

可以在文章的顶部下载这里控件的代码。

注意:这可能更容易使用两种方式处理 <span> 标签和只调整第二个内容,但这不适合本文的目的,因为JavaScript只是一些行。

控件本身很简单,两个 ViewState -enabled属性( .Text.InitialValue ) 带有 Render() 覆盖。 如果你已经读到这么远了,你就知道怎么做了。 这篇文章只对开发者感兴趣。

脚本的注册方式如下:

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]protectedvirtualstring IncrementScriptName
{
 get {
 return"ClickCounter_IncrementValue";
 }
}protectedoverridevoid Render(HtmlTextWriter output)
{
 if (!Page.IsStartupScriptRegistered(IncrementScriptName))
 Page.RegisterStartupScript(IncrementScriptName, IncrementScript);// standard rendering code here}

设计 IncrementScriptName 属性可以使继承者在脚本文本和脚本文本之间重写,同时在生成的页面中包括两种类型的控件,而无需重写更为复杂的方法。

现在看一下 IncrementScript 属性代码,它是与本文更相关的代码。

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]protectedvirtualstring IncrementScript
{
 get {#if DEBUG JavaScriptBuilder jsb = new JavaScriptBuilder(true);#else JavaScriptBuilder jsb = new JavaScriptBuilder();#endif jsb.AddCommentLine("Splits the inner text" + 
 " into Text (txt) and Counter (c) parts,");
 jsb.AddCommentLine("increments (c) and" + 
 " joins them back together again.");
 jsb.AddLine("function", IncrementScriptName, "(elmt)");
 jsb.OpenBlock(); // function (elmt) jsb.AddCommentLine("Initialize variables");
 jsb.AddLine("var inner = elmt.innerText;");
 jsb.AddLine("var c = 0;");
 jsb.AddLine("var txt ="";");
 jsb.AddLine();
 jsb.AddCommentLine("Run through inner text string");
 jsb.AddLine("for (idx = 0; idx <inner.length; idx++)");
 jsb.OpenBlock(); // for (idx...) jsb.AddCommentLine("Split string into text and counter parts");
 jsb.AddLine("c = parseInt( inner.substring (idx, inner.length + 1) );");
 jsb.AddLine("txt = inner.substring(0, idx);");
 jsb.AddLine();
 jsb.AddCommentLine("If we have a number, get out of the loop");
 jsb.AddLine("if (! isNaN( c ) )");
 jsb.OpenBlock(); // if (! isNaN( c ) ) jsb.AddLine("break;");
 jsb.CloseBlock(); // if (! isNaN( c ) ) jsb.CloseBlock(); // for (idx...) jsb.AddLine();
 jsb.AddCommentLine("Increment counter");
 jsb.AddLine("c++;");
 jsb.AddCommentLine("Rebuild the string and put it in the inner text"); 
 jsb.AddLine("elmt.innerText = txt +"" + c;");
 jsb.CloseBlock(); // function (elmt)return jsb.ToString();
 }
}

在调试模式下编译时,会生成以下代码块:

<script language="javascript">
<!--// Splits the inner text into Text (txt) and Counter (c) parts,// increments (c) and joins them back together again.function ClickCounter_IncrementValue(elmt)
{
 // Initialize variablesvar inner = elmt.innerText;
 var c = 0;
 var txt = "";
 // Run through inner text stringfor (idx = 0; idx <inner.length; idx++)
 {
 // Split string into text and counter parts c = parseInt( inner.substring (idx, inner.length + 1) );
 txt = inner.substring(0, idx);
 // If we have a number, get out of the loopif (! isNaN( c ) )
 {
 break;
 }
 }
 // Increment counter c++;
 // Rebuild the string and put it in the inner text elmt.innerText = txt + "" + c;
}// --></script>

这在控件本身中非常可以读并且在生成的页面中非常。 同时实现了对 StringBuilder.Append()的重复调用,没有单一的转义字符或者 Environment.NewLine

但是,它为最终用户生成了很多不必要的代码。 如果你每天有 1000个页面,并且每天访问 1000次,那么它们几乎不会看代码,每天的每额外字节的带宽是每额外字节的1Mb。

控件的发布版本将有一段较短的代码:

<script language="javascript">
<!--function ClickCounter_IncrementValue(elmt) { var inner = elmt.innerText; var c = 0; var txt = ""; for (idx = 0; idx <inner.length; idx++) { c = parseInt( 
inner.substring (idx, inner.length + 1) ); txt = inner.substring(0, idx); if ( 
! isNaN( c ) ) { break; } } c++; elmt.innerText = txt + "" + c; }// --></script>

完全不可读,但功能没有改变。 这个新版本在保存 400字节的情况下大大缩短。 每天访问 1000页的带宽总量每天都有4 次。 想象一下对一个真正复杂的JavaScript的影响。

摘要

JavaScriptBuilder 可以作为. cs ( C# ) 类文件包含在代码中,也可以从非常小的( 16kb ) DLL中链接。 它可以用于创建在生成页面中可以读的JavaScript代码,并且容易在控制源中维护。

忘记在维护和效率之间选择,选择两者。

注意:如果有任何可以用的内容,可以使用它,但是如果你决定使用它,则很好。 如果你有任何改进的话,我也想听听。


相关文章