日期:2010-06-08  浏览次数:21020 次

  笔者日前涉及一个大型ASP项目的开发,其中多次遇到多维下拉菜单(对于WEB项目而言,这里专指网页中的<SELECT>元素)的问题,菜单中的数据均需要从数据库中取出,并动态的生成和变化。笔者以前曾发表过如何利用PHP和JavaScript制作二维下拉菜单的文章,目前这类文章在网络上也颇为多见,思路也有很多创新,但对于本文中谈到的多维下拉菜单,很少有人谈及。笔者无意班门弄斧,只是想把开发中的一点经验和技巧总结出来,希望能给广大的读者一点启示。

  多维下拉菜单,顾名思义,也就是根据一个下拉菜单的选择,来控制其它一个或多个下拉菜单中显示的数据。举个例子来说明,在一个WEB管理系统中,用户要求通过选择单位名称,进而选择部门名称,最后选择员工。也就是说,需要提供三个下拉列表,每个下拉列表之间需要建立关联。通过第一个能选择第二个,并同时选择第三个,第四个等等。那么每一个下拉列表的显示数据之间如何建立关联,关联起来的数据又如何通过事件驱动,这正是本文所要讨论的主要内容。

  熟悉VB、Delphi等RAD开发工具的朋友可能会感到疑惑。的确,在这些所谓的RAD开发工具中,我们可以利用Combo Boxes控件很容易的实现下拉菜单,进而实现他们之间的关联。但是由于WEB项目中的下拉菜单是利用HTML中的<SELECT>元素来实现的,而由于HTML的局限性,无论是对象的属性还是事件模型,都远没有RAD工具那么强大。所以,开发WEB项目,这种问题只能有一个解决途径,那就是利用JavaScript(也可能有人说可以利用java来实现,那我也只好说,您是高手)。
关于JavaScript的使用,不是本文的重点,所以不了解或不熟悉JavaScript的读者请先参考JavaScript的相关资料以获取相关信息。

  好,言归正传,下面我们就一起来探讨多维下拉菜单的设计问题。为了讨论的方便,我们就以上文提到的三维下拉菜单为例,向大家一步一步的讲述设计的思路。

  一、分析菜单的运作流程

  首先,用户会选择单位列表,并从中选出一个单位名称,我们假定为单位A。这时,另外两个下拉列表应该做些什么?对,我们希望第二个下拉菜单能立即反映第一个下拉菜单的选择,显示并仅显示单位A中的所有部门,我们再假定菜单中第一项为部门A(默认的显示项)。那么可能有读者会问,第三个下拉菜单不是应该同时选择与部门A对应的员工数据吗?这是个很好的想法,是的,我们也应该立即改变第三个下拉菜单中的数据为部门A中的员工列表。同样,当用户选择部门时,又会改变员工列表。依次类推。
以上是一种思路,可能有一些特殊的情况,例如,在改变部门列表时,并不希望立即就选择一个部门,而是显示一个"请选择"字样的提示条目。
思路已经有了,下面就是如何实现的问题了。

  二、菜单数据的容器

  根据常规想法,当用户从第一个下拉菜单中选择单位名称,我们可以从数据库中选出与之相关的部门数据,并显示出来,这似乎也不无可行。但有经验的开发者就会发现,由于Web页面的无状态性,当你再次连接数据库时,Web页面必须得再次刷新。这是一个头疼的问题,一方面我们想连接数据库,可另一方面我们必须得保持用户已输入数据不被破坏。即使如此,估计用户也并不希望看到一个每次选择都刷新一次的局面。难道就没有更好的办法?

  有,那就是利用JavaScript的多维数组。我们为什么不可以把需要显示的数据在第一次连接数据库时全取出来,放到数组中去?这样在每次改变菜单数据时,只要从数组中取得数据,不就可以大大的提高效率了吗?这是个令人振奋的方法,这个方法中提到的JavaScript数组,我们暂且称之为菜单数据的容器。

  您的思路是不是一下子豁然开朗?可是跃跃欲试一番以后,是不是感到事情好像并不是那么简单?问题又来了,容器的结构该如何设计,数据之间的关联又如何实现呢?别急,其实这正是问题之所在。

  三、数据容器结构的设计

  说起容器结构的设计,我们得感谢数据结构中的链表给我们的启示--链表是通过指针联系在一起的。虽然JavaScript中没有指针的概念,但我们为什么不可以模拟一下。

  为了讨论方便,我们假定数据库的结构如下:

1、 单位信息表:(unit_id, unit_name, …)
2、 部门信息表:(dept_id, unit_id, dept_name, …)
3、 员工信息表:(emp_id, dept_id, emp_name, …)

  利用这个数据库结构,我们可以很容易的推导出数组的结构。您说的没错,这应该是一个多维数组。其定义方法应该象下面这样(以部门为例):

var arrDept = new Array();
arrDept[0] = new Array(unit_id0, dept_id0 dept_name0);
arrDept[1] = new Array(unit_id1, dept_id1, dept_name1);

arrDept[n] = new Array(unit_idn, dept_idn, dept_namen);

  n的大小视实际数据量而定,例如在单位下拉菜单中,n代表单位的总数。但读者必须明白,正是由于n的不确定性,以上的代码必须通过程序动态的产生。例如对于ASP程序,我们可以在<script></script>之间嵌入这样的一段代码:

<%
Dim rs, i
'[连接数据库,取出数据]
response.write "var arrDept = new Array();" & vbNewLine
i = 0
while not rs.EOF
response.write "arrDept[" & i & "] = new Array('" & rs(unit_id) & "', '" & _
rs(dept_id) & "', '" & rs(dept_name) & "');" & vbNewLine
rs.MoveNext
i = i +1
wend

%>

代码拷贝框

[Ctrl+A 全部选择 然后拷贝]

  以上这段代码用来从部门表中取出数据,并产生相关的JavaScript多维数组。这只是笔者的一种演示,读者完全可以使用更灵活的方法来提取数据。

  说来说去,我们还是要回到JavaScript数组的结构定义上来。聪明的读者应该已经从上述的代码中发现了数组的定义方法,但笔者还是要不厌其烦的再补充一遍:

  我们把数组的第一个元素定义为指针,用来指向其"父结点"。等等,什么是父结点?父结点说明白了就是上一级结点,例如,部门的上一级是单位,员工的上一级是部门。那么第二个元素是什么?让我们来看一下下面的一段<SELECT>定义:

<SELECT NAME="s1" >
<OPTION Value="1">单位1</OPTION>
<OPTION Value="2">单位2</OPTION>
….
</SELECT>

  <OPTION>元素的Value属性从哪里来呢?对,就是第二个元素,依此类推,第三个元素指的就是显示在菜单中的数据喽,即上面的"单位1"、"单位2"…

  读者到这里可能有些糊涂了,说这么多,这个数组到底是什么样?别急,让我们以部门为例,给出一段根据部门库中的数据动态生成的数组模拟代码:

<SCRIPT LANGUAGE="JAVASCRIPT">
<!-

var arrDept = new Array();
arrDept[0] = new Array('u