在我们的日常生活中,经常遇到过这样的页面:

当你对“所在省”进行操作以选择不同的省份时,“所在城市”的下拉列表将根据你选择的省份,自动列出该省份的所有城市。 这种场景我们已经司空见惯,并且,也有成套的解决方案。但将所有的解决方案都摆到桌面上,却发觉,这些解决方案并不是最优的,也不见得是完美的, 而且,其可扩展性也未必好。 举个最简单的例子,倘若再加一个下拉列表“国家”,并且需要“国家”、“省份”、“城市”三者进行联动,我们的解决方案是否能够自行适应呢? 再或者,倘若是“国家”、“省份”、“城市”、“区县”四者联动呢?甚至于当你选择某个直辖市时,要求“城市”所在的下拉列表变成readonly的时候, 我们现有的解决方案是否能够适应呢?
让我们首先来看看常规解决方案是怎样解决此问题的吧。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script language="JavaScript">function change_area() {
var cmbProvince = document.getElementById("province");
var cmbCity = document.getElementById("city");
var length = cmbCity.options.length;
for(var i=length-1; i>=0; i--) {
cmbCity.options[i] = null;
}
var province = cmbProvince.options[cmbProvince.selectedIndex].value;
if(province == "北京") {
cmbCity.options[0] = new Option("北京", "北京");
}
if(province == "上海") {
cmbCity.options[0] = new Option("上海", "上海");
}
if(province == "广东") {
cmbCity.options[0] = new Option("广州", "广州");
cmbCity.options[1] = new Option("深圳", "深圳");
cmbCity.options[2] = new Option("阳江", "阳江");
}
if(province == "安徽") {
cmbCity.options[0] = new Option("合肥", "合肥");
cmbCity.options[1] = new Option("黄山", "黄山");
cmbCity.options[2] = new Option("宿州", "宿州");
}
}
</script>
<title>选择地区</title>
</head>
<body>
<form method="post">
<%
String province = "北京";
String city = "北京";
if (request.getParameter("province") != null) province = request.getParameter("province");
if (request.getParameter("city") != null) city = request.getParameter("city");
out.println("<h2>你现在选择的是:" + province + "," + city + "</h2>");
%>
<br>省:<select onchange="javascript:change_area();" id="province" name="province">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广东">广东</option>
<option value="安徽">安徽</option>
</select> 市: <select id="city" name="city">
<option value="北京">北京</option>
</select>
<br>
<script>
var cmbProvince = document.getElementById("province");
var cmbCity = document.getElementById("city");
for(var i=0; i<cmbProvince.options.length; i++) {
if(cmbProvince.options[i].value == '<%=province%>') {
cmbProvince.options[i].selected = true;
change_area();
}
}
for(var i=0; i<cmbCity.options.length; i++) {
if(cmbCity.options[i].value == '<%=city%>') {
cmbCity.options[i].selected = true;
}
}
</script>
<input type="submit" value="提交">
</form>
</body>
</html>
![]() |
Java Script函数,在此函数中根据用户选择不同的省以决定右边的下拉列表框应该列出不同的市 |
![]() |
Java代码片断,根据Request中取得的参数,获得用户选择的省、市。 |
![]() |
定义下拉列表框,并且在第一个下拉列表控件上设置当其值发生变化时,调用change_area函数 |
![]() |
Java Script代码,根据Request中取得的参数,设置下拉列表的值为用户当前选择值 |
首先我们必须要指出的是,上述代码是最简易最直白的方式,我们当然可以对其进行优化,譬如:Java Script的生成可以通过后台Java Bean做到,这样可以将业务数据往后台放(无疑,省、市这些数据是业务数据);再譬如, 我们还可以通过Struts完成对Request参数的解析,以使其披上一层所谓的 MVC 的外衣。但无论怎么优化,我们始终无法解决两个根本问题:开发效率低,可扩展性差。
上述常规解决方案的开发效率无疑是非常低下的,因为其本质就是通过Java代码片断拼凑HTML与Java Script。组件技术是提升软件复用度进而提升软件开发效率的有效手段,而在上述代码中,我们根本就没能发现任何组件的思想,其代码臃肿度也可见一斑。
我们来看看,如果上述程序用Opera Masks技术来进行改造,那么,程序又会变成什么样子:
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%><%@taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@taglib uri="http://www.apusic.com/jsf/widget" prefix="w"%>
<%@taglib uri="http://www.apusic.com/jsf/layout" prefix="layout"%>
<%@taglib uri="http://www.apusic.com/jsf/ajax" prefix="ajax"%>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h"%><f:view renderKitId="AJAX">
<w:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </w:head>
<w:page title="选择地区">
<w:form>
<layout:panelGrid columns="4">
<layout:cell colspan="4" rowspan="1">
<h2><h:outputText value="#{areaBean.display}"></h:outputText></h2>
</layout:cell>
<layout:cell colspan="1" rowspan="1">省</layout:cell>
<layout:cell colspan="1" rowspan="1">
<w:combo id="province" value="#{areaBean.province}" typeAhead="true">
<ajax:action event="onchange"></ajax:action>
<f:selectItems value="#{areaBean.provinces}" />
</w:combo>
</layout:cell>
<layout:cell colspan="1" rowspan="1">市</layout:cell>
<layout:cell colspan="1" rowspan="1">
<w:combo id="city" binding="#{areaBean.cmbCity}" typeAhead="true">
<f:selectItems value="#{areaBean.cities}" />
</w:combo>
</layout:cell>
<layout:cell colspan="4" rowspan="1">
<w:button value="提交" />
</layout:cell>
</layout:panelGrid>
</w:form>
</w:page>
</f:view>
package demo;@ManagedBean(name="areaBean", scope=ManagedBeanScope.SESSION)
public class AreaBean {
@ManagedProperty
private String province = "北京";
@ManagedProperty
private UICombo cmbCity;
@ManagedProperty
private String[] provinces = new String[]{
"北京", "上海", "广东", "安徽"
};
private Map<String, String[]> cities = new HashMap<String, String[]>();
public AreaBean() {
super();
cities.put("广东", new String[]{"广州", "深圳", "清远", "阳江"});
cities.put("安徽", new String[]{"合肥", "黄山", "宿州", "蚌埠"});
cities.put("北京", new String[]{"北京"});
cities.put("上海", new String[]{"上海"});
}
public String[] getCities() {
return cities.get(this.province);
}
public void setCmbCity(UICombo cmbCity) {
this.cmbCity = cmbCity;
if(cmbCity != null) cmbCity.setValue("北京");
}
public String getDisplay() {
return "你现在选择的是:" + province + "," + this.cmbCity.getValue();
}
public void setProvince(String province) {
if(this.province.equals(province)) return;
this.province = province;
if("北京".equals(province) || "上海".equals(province)) {
this.cmbCity.setReadonly(true);
}
else {
this.cmbCity.setReadonly(false);
}
this.cmbCity.setValue(getCities()[0]);
}
}
初一看改造后的示例,感觉代码量好像并没有少许多,但仔细分析下来,你会发觉,有如下优势:
在常规解决方案中,UI部分与后台的业务模型是耦合在一起的,这也是造成扩展性差、维护成本高的主要原因。“MVC”已经是目前过度泛滥的词汇之一, 但事实上,很多 web 开发解决方案并没有彻底的解决此问题。以本文描述的场景为例,当需要动态生成 java script 以达到更好的客户端交互的时候,Struts 便无能为力。
我相信,绝大部分的 J2EE 开发人员看了第二种解决方案后,都不会再对第一种解决方案抱有好感,究其原因无非是第二种开发方案能够带来开发效率的大幅提升。组件模型、服务器端事件,都给开发人员带来了巨大的便利, 再辅以Apusic Studio可设化的设计,一体化的过程,相信这样一种开发方法,会使每一位 J2EE 开发人员都感到轻松与方便。
如果我们需要再加一个下拉列表“国家”,并且需要“国家”、“省份”、“城市”三者进行联动,甚至加更多的下拉列表,或者其它的功能扩展,我们只需在 view 中对 UI进行重新设计,再在后台补充相应的业务逻辑与事件响应,一切尽在掌握。
这个跟ajax4j有什么区别
除了通过annotation声明managed-bean(这个又跟seam有什么区别) 感觉这个示例就是seam+richfaces
function change_area() {
String province = "北京";
省:<select onchange="javascript:change_area();" id="province" name="province">
var cmbProvince = document.getElementById("province");
<ajax:action event="onchange"></ajax:action>
<w:combo id="city" binding="#{areaBean.cmbCity}" typeAhead="true">
public void setProvince(String province) {
經典
Kevin大哥能多出些文章和教程嗎? 支持您!