优化的主/细节 DropDownList

分享于 

14分钟阅读

Web开发

  繁體 雙語

更新摘要

不早我使用了原文中描述的控件,当我需要'二'详细信息时,需要从主DDL驱动。 因为我知道当需要'三'时,我将广泛的代码用于支持任何数量的'slaved'数据。 我做的其他更改是在加载页面时提供详细的ddl。

逻辑和功能仍然相当相同,除了使用 array的一个细节DDL来初始化主DDL的数据。 当然,客户端脚本更改为加载一个 array的详细信息而不仅仅是一个。

介绍

ASP很棒。它允许你在服务器上做任何事情。 然而,这并不是最有效的解决方案。

一个常见的设计要求是选择一个列表的内容来确定第二个列表的内容。 这通常被描述为'主/明细'关系,其中第二个列表的内容取决于'母版'或者主列表的选择。 通常你会填充主列表,默认选择第一项并使用它作为填充第二个列表的键。 然而,一旦用户在主列表上选择了一个不同的项目,需要使用适当的项目更新'详细信息'列表。

一个解决方案是让填充页面中的细节列表所需的数据。 当用户在主列表上做出选择时,数据将在客户机上可用以填充详细列表。 通常需要填充详细列表的数据量相当小,并且可以嵌入到页面中。 面对它,用户不希望滚动几百个项目来做出选择。

本文提出的方法是'简单和 compact'解决方案,利用浏览器对 XML ( DOM )的支持和数据集生成XML的功能。

生成MasterDDL控件

创建一个新的Web控件并从DropDownList派生它。 添加一个属性如下所示。

#region private membersprivate Array m_SlaveData;#endregion#region public propertiespublic Array SlaveData
{
 set{m_SlaveData = value;}
}#endregion

属性允许用户指定将用于填充每个DetailDDLs的数据的源。 array 用于存储信息,由数据集。DetailDDL的NAME ( 标识符) 和表 NAME ( 数据) 组成。 接下来,重写两个基类方法,这些方法允许我们在客户机上创建数据和代码来填充 DetailDDLs。

protectedoverridevoid AddAttributesToRender(
 HtmlTextWriter writer) 
{
 base.AddAttributesToRender(writer);
 if (m_SlaveData.Length!= 0)
 {
 writer.AddAttribute("onClick", "doMasterClick()");
 }
}protectedoverridevoid Render(HtmlTextWriter output)
{
 //Write out the data for the slave ddlif (m_SlaveData.Length!= 0)
 {
 //Write out the data for each of the detail DDLsfor (int nCount = 0; nCount < m_SlaveData.GetLength(0); nCount++)
 {
 StringBuilder s1 = new StringBuilder("dso");
 s1.Append(m_SlaveData.GetValue(nCount,0).ToString());
 output.Write("<xml id="+s1.ToString()+">n");
 DataSet ds = (DataSet)m_SlaveData.GetValue(nCount,1);
 ds.WriteXml(output);
 output.Write("</xml>n");
 }
 //First the script that will be called to load the slave ddl StringBuilder s = new StringBuilder("n<script language="JavaScript">n");
 s.Append("function doMasterClick()n");
 s.Append("{n");
 //The array of slave ddl(s) s.Append("var theSlaveDDLs = new Array(");
 for (int nCount = 0; nCount < m_SlaveData.GetLength(0); nCount++)
 {
 if (nCount!= 0)
 s.Append(",");
 s.Append("""+ m_SlaveData.GetValue(nCount,0).ToString() + """);
 s.Append(",");
 s.Append("""+ m_SlaveData.GetValue(nCount,2).ToString() + """);
 }
 s.Append(")n");
 //Get the current selection from the master ddl s.Append("var sel = document.all['" + this.ID + "'].value;n");
 //For each slave DDL... s.Append("for(i=0;i<theSlaveDDLs.length;i+=2)n");
 s.Append("{n");
 s.Append("var optCount = 0;n");
 s.Append("var dso = new String("dso"+theSlaveDDLs[i]);n");
 s.Append("SlaveData = document.all[dso].XMLDocument;n");
 //First erase the contents of the slave ddl, if any s.Append("while(document.all[theSlaveDDLs[i]].length)n");
 s.Append("document.all[theSlaveDDLs[i]].options[0] = (null,null);n");
 //Now add the new ones s.Append("for(j=0;j<SlaveData.childNodes(0).selectNodes(theSlaveDDLs[i+1])");<BR> s.Append( ".length;j++)n");
 s.Append("{n");
 s.Append("var data = SlaveData.childNodes(0).selectNodes(");<BR> s.Append( "theSlaveDDLs[i+1])(j);n");
 s.Append("if(sel == data.childNodes(1).text)n");
 s.Append("{n");
 s.Append("var option = new Option(data.childNodes(2).text,");<BR> s.Append( "data.childNodes(0).text);n");
 s.Append("document.all[theSlaveDDLs[i]].options[optCount] = option;n");
 //Save the first entry as the default selection s.Append("if (optCount==0)n");
 s.Append("{n");
 s.Append("var s1 = new String(theSlaveDDLs[i] +"_Sel");n");
 s.Append("document.all[s1].value = data.childNodes(0).text;n");
 s.Append("var s2 = new String(theSlaveDDLs[i] +"_Val");n");
 s.Append("document.all[s2].value = data.childNodes(2).text;n");
 s.Append("}n");//end if s.Append("optCount = optCount+1;n");
 s.Append("}n");//end if s.Append("}n");//end for(j... s.Append("}n");//end for(i... s.Append("}n");//end function s.Append("<" + "/" + "script>n");
 output.Write(s.ToString());
 }
 // draw our controlbase.Render(output);
}

第一个替代仅仅提供了钩子脚本函数的方法。 第二种情况是大多数动作发生,所以我将描述一些代码。 第一个'循环'负责写出详细的ddl数据。 大部分工作是由数据集本身完成的,所有我们必须做的就是用一些XML标记包装数据。 你可以看到在构建测试应用程序时查看结果页的源代码时发生了什么。 浏览器将把数据加载到内存文档对象中,你可以从代码中对它的进行操作。 你可以从 http://www.w3.org/DOM/ 获取有关文档对象模型和浏览器支持的其他信息。

doMasterClick 函数内部,我们首先创建一个保存详细信息列表列表的array,以便我们可以循环访问它们。 接下来我们找到主DDL并获取它的当前选择。 函数的其余部分是主循环,我们将数据加载到指定的每个详细ddl中。 首先,我们将对各个数据岛的引用作为'slavedata'。 然后我们找到DetailDDL并清除它可能有的任何内容。 最后,只需遍历数据寻找匹配当前MasterDDL选择的'值'的所有项。 对于每个 MATCH,我们在细节DDL中创建一个条目,并相应地设置'值'和'数据'。

DetailDDL控件

如果你只想向用户提供信息,那么一切都会正常,你只需使用一个规则DDL控件。 然而,大多数时候我们需要知道用户在DetailDLL上选择哪些项目,以便我们能够执行某些操作。 因为DetailDLL是动态地在客户机上填充的,所以没有机制可以发送所选项目。 通常,当在服务器上使用 static 列表在服务器上填充listviewitem时,通常会使用of机制来自动完成。

我将在这里介绍的解决方案是为DetailDDL创建一个定制的DropDownList。 为了方便需求,需要DetailDLL在客户机上持久化它的选择状态,然后在页面发布时发送它。 我们可以通过让DetailDDL创建两个隐藏输入字段,然后发出一些脚本,用户选择这些字段。 下面是代码:

publicclass DetailDDL: 
 System.Web.UI.WebControls.DropDownList, IPostBackDataHandler 
{
 #region private membersprivatestring m_strTableName;
 #endregion#region public propertiespublicstring TableName
 {
 set{m_strTableName = value;}
 }
 #endregionprotectedoverridevoid AddAttributesToRender(
 HtmlTextWriter writer) 
 {
 base.AddAttributesToRender(writer);
 writer.AddAttribute("onClick", "doSlaveClick(this.value)");
 }
 protectedoverridevoid OnInit(
 EventArgs e)
 { 
 base.OnInit(e);
 this.Items.Add("");
 }
 protectedoverridevoid OnPreRender(
 EventArgs e)
 {
 base.OnPreRender(e);
 if (Page!= null)
 {
 Page.RegisterHiddenField(ClientID+"_Sel", "");
 Page.RegisterHiddenField(ClientID+"_Val", "");
 }
 }
 protectedoverridevoid Render(
 HtmlTextWriter output)
 {
 StringBuilder s = new StringBuilder("n<script language="JavaScript">n");
 s.Append("function doSlaveClick(val)n");
 s.Append("{n");
 s.Append("var dso = new String("dso"+this.ID+"");n");
 s.Append("SlaveData = document.all[dso].XMLDocument;n");
 s.Append("for(j=0;j<SlaveData.childNodes(0).");<BR> s.Append( "selectNodes(""+m_strTableName+"").length;j++)n");
 s.Append("{n");
 s.Append("var data = SlaveData.childNodes(0).selectNodes(""+");<BR> s.Append("m_strTableName+"")(j);n");
 s.Append("if(val == data.childNodes(0).text)n");
 s.Append("{n");
 s.Append("ndocument.all['"+ClientID+"_Val"+"'].value =");<BR> s.Append( "data.childNodes(2).text;");
 s.Append("nbreak;");
 s.Append("}n");
 s.Append("}n");
 s.Append("ndocument.all['"+ClientID+"_Sel"+"'].value = val;");
 s.Append("n}n");
 s.Append("<" + "/" + "script>n");
 output.Write(s.ToString());
 // draw our controlbase.Render(output);
 }
 bool IPostBackDataHandler.LoadPostData(
 string postDataKey, 
 NameValueCollection postCollection)
 {
 string sel = postCollection[ClientID+"_Sel"];
 string val = postCollection[ClientID+"_Val"];
 if (sel == null || val == null)
 {
 this.SelectedIndex = -1;
 }
 else {
 this.Items[0].Value = sel;
 this.Items[0].Text = val;
 this.SelectedIndex = 0;
 }
 returnfalse;
 }
}

要注意的唯一一点是我们已经实现了IPostBackDataHandler接口。 这允许我们在后回进程中进行 particiapate。 在第一个重写中,我们将客户端脚本函数与MasterDDL中所做的操作。 第二个替代只是有点 fudge。 我们重写 OnPreRender 以在客户机上创建两个隐藏字段以保存所选值。 在 Render() 中,我们发出的脚本将在用户单击详细DDL时在客户机上执行。 最后我们实现 LoadPostData() 方法,以便从隐藏字段检索值并设置DetailDLLs值,以便服务器代码可以使用它们。

在更新的版本中,我添加了一个属性,以便详细信息DDL可以定位它的数据岛。 在以前的版本中,选中的'值'被返回,但不是选择的'数据'。 大多数情况下这不会是一个问题,因为我们通常感兴趣的是所选项目的ID。 在这个更新版本中,细节DDL脚本定位它的数据岛,迭代查找所选项目的MATCH,并正确地填充隐藏字段。

使用 DropDownList

演示项目演示了如何使用 dll,而与常规代码不太一样。 要使用主/细节listviewitem控件,需要添加对 MasterDetail.dll的引用并将它的添加到工具箱中。 演示项目使用'pubs'数据库,但是如果你不能够使用它,那么你可以轻松修改代码以使用另一个数据库。

在演示项目中,你还注意到我已经在事件中挂钩,以便在初始化页时调用'doMasterClick()'方法。 详细信息DDL将根据主DDL中当前选择的内容加载。

关于从数据集的表所需排序的最后一个说明。 上代码要求表按以下顺序具有三个字段: 我已经在演示项目中留下了注释部分( 从原始文章),演示了如何通过两个表的联接来完成这个任务。

奖金 !

你知道你可以像服务器端一样轻松调试客户端代码? 从 IE 菜单项工具中选择高级选项卡。internetoptions。 确定'禁用脚本调试'项目已经清除。 现在,当页面被加载选择视图。"。scriptdebugger。"。打开时,你将得到页面的源页面。 现在,你可以设置断点。查看变量和单步执行代码,就像使用服务器代码一样。

历史记录

30 2003年01月 - 原始提交。
22 2003年02月 - 修改代码以支持任意数量的DetailDDLs,并相应地修改了测试应用程序。


相关文章