Web页中级联下拉选择框问题的解决方法
示范中心项目里有一些页面要求几个下拉选择框的内容是具有关联的关系的,例如在编辑一个实验项目时,要先在一个下拉框里选择该项目所在的示范中心,这时实验室下拉框里的内容要根据用户选中的示范中心改变。为了实现这个目的,我们先后想了几种方法。
1、在用户选择示范中心时,刷新页面,并把示范中心代码加在url的后面传给action。这样的最大问题是,如果用户已经填写了一部分表单,在更改示范中心选择的时候会丢失已填写的信息。除非在刷新时把整个表单的所有域都加在url里传回,但这样做要在jsp页面里增加非常多的逻辑,因此不能使用。
2、把与示范中心关联的几个下拉框作为iframe的方式,当选择了一个示范中心时,根据所选的示范中心重新load这个iframe,这样不会影响到其他可能已经填写的域。这样做的问题是,iframe对应的页面里要重复考虑权限问题,这是不能忍受的,同时和所有使用框架技术的应用一样,有可能造成一些页面不一致的情形。
3、前面两种方式虽然都被否决了,但它们共同的好处是在用户改变对示范中心的选择时,数据是动态加载的。第三种方式是把所有数据一次读到客户端,使用javascript的方法实现关联。我在网上找到了一些js代码,但大部分都不适合动态生成数据,这也是一开始没有使用js方式的原因之一。不过最后还是找到了一个我认为不错的js,对它进行少量修改后比较漂亮的完成了任务,下面就说说具体的方法。
这个js的演示和下载地址在http://fason.nease.net/samples/select/,这里我要感谢这位fason朋友提供这么好的工具。在需要关联的页面里,例如编辑实验项目的页面,首先要引入xselect.js这个script,然后构造一个Dsy对象,使用这个对象的add方法增加关联下拉框的各个选项。还有两个数组,sel是关联下拉框的id数组,def是这些下拉框缺省的选项值数组。最后使用attachSelect函数将dsy对象和这两个数组关联起来就大功告成。对于选项是动态生成的情况,就是要动态生成这么一段javascript,如下所示:
<script language="JavaScript" src="../xselect.js"></script> <script language="JavaScript"> <!-- var dsy = new Dsy(); var sel = ["demoCenter","lab"]; var def = ['<bean:write name="projectForm" property="demoCenterCode"/>','<bean:write name="projectForm" property="labCode"/>']; dsy.add("0",[['<bean:message key="select.prompt" bundle="root"/>',''] <logic:iterate name="<%=DemoCenter.class.getName()%>" id="dc"> ,['<bean:write name="dc" property="name"/>','<bean:write name="dc" property="code"/>'] </logic:iterate> ]); dsy.add("0_0",[['<bean:message key="select.prompt" bundle="root"/>','']]); <logic:iterate name="<%=DemoCenter.class.getName()%>" id="dc" indexId="index"> dsy.add('0_<%=index.intValue()+1%>',[['<bean:message key="select.prompt" bundle="root"/>',''] <bean:define id="code" name="dc" property="code" type="String" /> <logic:iterate name="<%=Lab.class.getName()%>" id="lab"> <logic:equal name="lab" property="demoCenter.code" value="<%=code%>"> ,['<bean:write name="lab" property="name"/>','<bean:write name="lab" property="code"/>'] </logic:equal> </logic:iterate> ]); </logic:iterate> attachSelect(dsy,sel,def); //--> </script>
生成的静态页面就是下面这样,页面显示后会缺省选择“北京大学数学示范中心”和“软件工程实验室”:
<script language="JavaScript" src="../xselect.js"></script> <script language="JavaScript"> <!-- var dsy = new Dsy(); var sel = ["demoCenter","lab"]; var def = ['10000001','10000007']; dsy.add("0",[['--------请选择--------',''] ,['教育部','10000000'] ,['北京大学数学示范中心','10000001'] ,['北京师范大学普通话示范中心','10000002'] ,['北京邮电大学卫星通信示范中心','10000003']]); dsy.add("0_0",[['--------请选择--------','']]); dsy.add('0_1',[['--------请选择--------',''] ,['生物实验室','10000004'] ,['软件工程实验室','10000007'] ]); dsy.add('0_2',[['--------请选择--------',''] ,['数据挖掘实验室','10000002'] ,['微电子实验室','10000003'] ,['Web服务实验室','10000006'] ]); dsy.add('0_3',[['--------请选择--------',''] ,['航空航天实验室','10000001'] ,['湍流实验室','10000005'] ,['互联网实验室','10000008'] ]); dsy.add('0_4',[['--------请选择--------',''] ]); attachSelect(dsy,sel,def); //--> </script>
不过这个js有个小问题,就是它的def数组中的每个元素只能是一个字符串,当选择框是可多选的列表框时,就不能实现多个选项的预选择(populate)。为了解决这个问题,我对xselect.js进行了少量的修改,允许def中的每个元素是一个数组。以下是修改后的xselect.js文件中的doChange()函数:
function doChange(v) { var str = "0"; for (var i = 0; i < v; i++) { str += ("_" + Sel[i].selectedIndex); }; var ss = Sel[v]; if (oDsy.Exists(str)) { with (ss) { length = 0; var ar = oDsy.Items[str], xx = 0; for (var i = 0; i < ar.length; i++) { var ot = ar[i][0], ov = ar[i][1] ? ar[i][1] : ot; //if (ov == Store[v]) xx = i; options[i] = new Option(ot, ov); //Added by zhanghao if(Store[v] instanceof Array){ for(var j=0;j<Store[v].length;j++) if(Store[v][j] == ov)options[i].selected = true; }else{ if(ov == Store[v])options[i].selected = true; } } //options[xx].selected = true; if (++v < Sel.length) doChange(v); } } else { for (var i = v; i<Sel.length; i++) { with (Sel[i]) { length = 0; options[0] = new Option("--", ""); options[0].selected = true; } } } }
其中注释掉了两句,并增加了一段对数组元素的处理,目前这个js在示范中心系统里工作良好。为了让jsp页面中的代码进一步减少,我们今后还要把动态生成js的代码写为tag的方式。
Web页中级联下拉选择框问题是一个非常普遍的问题,本贴里介绍的是我认为十分圆满的一个客户端方式的解决方法,用户在使用时不会感到任何不便,而且因为数据权限问题是在action里已经解决的,在对应的jsp页面里所做的任何处理都不需要考虑数据权限了。但在一些数据量特别大的系统里要考虑网络连接速度是否适合将所有数据传到客户端的问题。
Update: 相关代码下载