实例说明
多级联动下拉列表是指一组相互关联的下拉列表,相邻的两个下拉列表是父子关系,改变父下拉列表的值,子下拉列表也随之改变。运行本实例,如图1所示,在页面中将显示一个三级联动下拉列表,在省和直辖市的下拉列表框中选择省份,在地级市下拉列表中将显示出该省份的地级市信息,在地级市下拉列表中选择其中一个市,在县、县级市或区下拉列表中将显示区信息。

关键技术
在实现本范例时,首先需要设计一个保存省市信息的XML文件,在该文件中,需要充分体现出省/直辖市、地级市和县/县级市/区之间的关系。接下来需要从XML文件中获取所需的信息,也就是解析XML文件。本实例中应用的是dom4j组件解析XML文件,下面将对应用dom4j解析XML文件进行介绍。
1.构建XML文档对象
在解析XML文档前,需要构建要解析的XML文件所对应的XML文档对象。在获取XML文档对象时,首先需要创建SAXReader对象,然后调用该对象的read()方法获取对应的XML文档对象。SAXReader对象的read()方法的原型如下:
public Document read(Filefile) throws DocumentException
file:用于指定要解析的XML文件。
例如,获取XML文件zone.xml对应的XML文档对象的代码如下:
String fileURL =request.getRealPath("/xml/zone.xml");
SAXReader reader = newSAXReader(); // 实例化SAXReader对象
Document document =reader.read(new File(fileURL)); //获取XML文件对应的XML文档对象
2.获取根节点
在构建XML文档对象后,就可以通过该XML文档对象获取根节点。dom4j组件的Document对象的getRootElement()方法可以返回指定XML文档的根节点。getRootElement()方法的原型如下:
public ElementgetRootElement()
返回值:Element对象。
例如,获取XML文档对象document的根节点的代码如下:
Element country=document.getRootElement(); // 获取根节点
3.获取子节点
在获取根节点后,还可以获取其子节点,这可以通过Element对象的element()或elements()方法实现。下面将分别介绍这两个方法。
element()方法
element()方法用于获取指定名称的第一个节点。该方法通常用于获取根节点中节点名唯一的一个子节点。element()方法的原型如下:
public Element element(Stringname)
参数说明:
name:用于指定要获取的节点名。
返回值:Element对象。
elements()方法
elements()方法用于获取指定名称的全部节点。该方法通常用于获取根节点中多个并列的具有相同名称的子节点。elements()方法的原型如下:
public List elements(Stringname)
参数说明: name:用于指定要获取的节点名。
返回值:List集合。
例如,要获取本范例中的XML文件zone.xml的province节点,可以应用elements()方法,具体代码如下:
Element country =document.getRootElement(); // 获取根节点
List<Element>provinceList = country.elements("province"); // 获取表示省份和直辖市的节点
4.查询节点
在dom4j组件中,查询节点可以应用Element对象的selectSingleNode()方法实现。Element对象的selectSingleNode()方法用于获取符合指定条件的唯一节点,该方法的原型如下:
public NodeselectSingleNode(String xpathExpression)
参数说明:
xpathExpression:XPath表达式。XPath表达式使用反斜杠“/”隔开节点树中的父子节点,从而构成代表节点位置的路径。如果XPath表达式以反斜杠“/”开头,则表示使用的是绝对路径,否则表示使用的是相对路径。如果使用属性,那么必须在属性名前加上@符号。另外,在XPath表达式中,也可以使用谓词,例如下面的表达式将返回name属性值等于“北京市”的province节点。
/country/province[@name='北京市']
例如,应用selectSingleNode()方法获取XML文档的根节点country的name属性为“北京市”的子节点province的代码如下:
Element item = (Element)country.selectSingleNode("/country/province[@name='北京市']");
5.获取属性值
使用Element对象的attributeValue()方法可以获取指定节点的指定属性值,该方法的原型如下:
public StringattributeValue(String name)
参数说明:
name:参数用于指定要获取其值的属性名。
例如,要获取province节点的name属性的值,可以使用下面的语句:
ElementprovinceElement.attributeValue("name")
设计过程
(1)创建一个XML文件,名称为zone.xml,用于保存省市信息。Zone.xml文件的关键代码如下:
-
<?xml version="1.0" encoding="UTF-8"?>
-
<country name="中国">
-
<province id="00000" name="北京市">
-
<city id="00001" name="北京" area="东城区,西城区,崇文区,宣武区,朝阳区,丰台区,
-
石景山区,海淀区,门头沟区,房山区,通州区,顺义区,冒平区,大兴区,怀柔区,平谷区,
-
密云区,延庆县">
-
</city>
-
</province>
-
<province id="05000" name="吉林省">
-
<city id="05001" name="长春" area="双阳区,德惠市,九台市,农安县,榆树市,南关区,
-
宽城区,朝阳区,二道区,绿园区,经济技术开发区,高新区">
-
</city>
-
<city id="05002" name="延边朝鲜族自治州" area="延吉市,图们市,敦化市,珲春市">
-
</city>
-
<city id="05003" name="吉林" area="船营区,冒邑区,龙潭区,丰满区,蛟河市,桦甸市">
-
</city>
-
<city id="05004" name="白山" area="八道江区,江源区,临江区,抚松县,靖宇县">
-
</city>
-
<city id="05005" name="白城" area="洮北区,洮南区,大安市,镇赉县,通榆县,其他">
-
</city>
-
<city id="05006" name="四平" area="梨树县,伊通满族自治县,公主岭市,双辽市">
-
</city>
-
<city id="05007" name="松原" area="宁江区,长岭县,乾安县,扶余县,前郭">
-
</city>
-
<city id="05008" name="辽源" area="龙山区,西安区,东丰县,东辽县,其他">
-
</city>
-
<city id="05009" name="通化" area="东冒区,二道江区,梅河口市,集安市,通化县">
-
</city>
-
</province>
-
…… <!-- 省略了其他省市的节点信息 -->
-
</country>
(2)编写index.jsp文件,应用DIV+CSS进行布局,并在该文件的适当位置添加省/直辖市下拉列表、地级市下拉列表和县/县级市/区下拉列表,关键代码如下:
-
<select name="province" id="province">
-
</select>
-
-
-
<select name="city" id="city">
-
</select>
-
-
-
<select name="area" id="area">
-
</select>
(3)创建request.js文件,用于封装Ajax请求服务器的方法。
(4)在index.jsp页面的<script>标签中,首先导入request.js文件,然后在<script>标签内声明3个全局变量,分别是获取省份的XMLHttpRequest对象的全局变量、获取地级市的XMLHttpRequest对象的全局变量和获取县、区的XMLHttpRequest对象的全局变量。编写实例化Ajax对象的方法getProvince(),用于向服务器发送请求,获取省份和直辖市。关键代码如下:
-
<script language="javascript" src="js/request.js"></script>
-
<script language="javascript">
-
var provinceRequest = false; //声明获取省份的 XMLHttpRequest对象的全局变量
-
var cityRequest = false; //声明获取地级市的 XMLHttpRequest对象的全局变量
-
var areaRequest = false; //声明获取县、区的 XMLHttpRequest对象的全局变量
-
//获取省份和直辖市
-
function getProvince(){
-
var url="ZoneServlet"; //服务器地址
-
var param ="action=getProvince&nocache="+new Date().getTime(); //请求参数
-
request=httpRequest("post",url,true,callback_getProvince,param); //调用请求方法
-
}
(5)创建用于处理请求的Servlet实现类ZoneServlet,在该servlet的doPost()方法中根据获取的action参数值来调用不同的方法。doPost方法的具体代码如下:
-
protected void doPost(HttpServletRequest request, HttpServletResponse response)
-
throws ServletException, IOException{
-
String action = request.getParameter("action"); //获取请求中的action参数值
-
if ("getProvince".equals(action))
-
getProvince(request, response); //获取省份的方法
-
else if ("getCity".equals(action))
-
getCity(request, response); //获取地区市的方法
-
else if ("getArea".equals(action))
-
getArea(request, response); //获取县区的方法
-
}
(6)在ZoneServlet中,编写用于获取省份和直辖市的getProvince()方法。首先获取保存市县信息的XML文件的完整路径,然后判断该XML文件是否存在,如果存在,则通过dom4j组件解析该文件,从中获取出省份和直辖市并连接为以逗号分隔的字符串,最后设置应答的类型为HTML,并且输出由县和直辖市信息组成的字符串,如果没有获取到相关内容,则输出空的字符串。getProvince()方法的具体代码如下:
-
public void getProvince(HttpServletRequest request,HttpServletResponse response)
-
throws ServletException, IOException {
-
response.setCharacterEncoding("UTF-8"); //设置响应的编码方式
-
String fileURL = request..getRealPath("/xml/zone.xml"); //获取XML文件的路径
-
File file = new File(fileURL);
-
Document document = null; //声明Document对象
-
Element country = null; //声明根节点的Element
-
String result = "";
-
if (file.exists()) { //如果文件存在,则读取该文件
-
SAXReader reader = new SAXReader(); // 实例化SAXReader对象
-
try {
-
document = reader.read(new File(fileURL)); //获取XML文件对应的XML文档对象
-
country = document.getRootElement(); // 获取根节点
-
// 获取表示省份和直辖市的节点
-
List<Element> provinceList = country.elements("province");
-
Element provinceElement = null;
-
// 将获取的省份连接为一个以逗号分隔的字符串
-
for (int i = 0; i < provinceList.size(); i++) {
-
provinceElement = provinceList.get(i);
-
result = result + provinceElement.attributeValue("name")+ ",";
-
}
-
result = result.substring(0, result.length() - 1); // 去除最后一个逗号
-
} catch (DocumentException e) {
-
e.printStackTrace();
-
}
-
}
-
response.setContentType("text/html");
-
PrintWriter out = response.getWriter();
-
out.print(result); // 输出获取的市县字符串
-
out.flush();
-
out.close();
-
}
(7)在index.jsp页中编写获取省份直辖市的Ajax回调函数。首先需要将获取的省份名称字符串分隔为数组,然后通过循环将数组中的省份名称添加到下拉列表中,并且当下拉列表的第一个选项不为空时,还需要调用获取地级市的方法,获取默认的地级市信息。具体代码如下:
-
function callback_getProvince(){
-
if(request.readyState == 4){
-
if(request.status == 200){
-
provinceArr=request.responseText.split(","); //将获取的省份名称字符串分隔为数组
-
for(i=0;i<provinceArr.length;i++){ //通过循环将省份名称添加到下拉列表中
-
document.getElementById("province").options[i]=new Option(provinceArr[i],provinceArr[i]);
-
}
-
if(provinceArr[0]!=""){
-
getCity(provinceArr[0]); //获取地级市
-
}
-
request = false;
-
}
-
}
-
}
(8)为了让页面载入后,即可获取到省份和直辖市信息,还需要在窗口的onload事件中调用getProvince()函数。具体代码如下:
-
window.onload=function(){
-
getProvince(); //获取省份和直辖市
-
}
(9)在index.jsp页的<script>中,编写实例化Ajax对象的方法getCity(),用于向服务器发送请求,获取获取地级市。关键代码如下:
-
function getCity(selProvince){
-
var url="ZoneServlet"; //服务器地址
-
var param ="action=getCity&parProvince="+selProvince+"&nocache="+new Date().getTime();
-
request=httpRequest("post",url,true,callback_getCity,param); //调用请求方法
-
}
10)在ZoneServlet类中,编写用于获取省份或直辖市所对应的地区市信息的getCity()方法。在该方法中通过dom4j组件解析XML文件,从中获取出指定省份或直辖市所对应的地级市信息并连接为以逗号分隔的字符串,最后设置应答的类型为HTML,并且输出由地级市信息组成的字符串,如果没有获取到相关内容,则输出空的字符串。getCity()方法的具体代码如下:
-
public void getCity(HttpServletRequest request, HttpServletResponse response)
-
throws ServletException, IOException {
-
response.setCharacterEncoding("UTF-8"); // 设置响应的编码方式
-
String fileURL = request.getRealPath("/xml/zone.xml"); // 获取XML文件的路径
-
File file = new File(fileURL);
-
Document document = null; // 声明Document对象
-
String result = "";
-
if (file.exists()) { //如果文件存在,则读取该文件
-
SAXReader reader = new SAXReader(); // 实例化SAXReader对象
-
try {
-
document = reader.read(new File(fileURL)); //获取XML文件对应的XML文档对象
-
Element country = document.getRootElement(); // 获取根节点
-
String selProvince = request.getParameter("parProvince");//获取的省份
-
selProvince = java.net.URLDecoder.decode(selProvince,"UTF-8");
-
Element item = (Element) country.selectSingleNode("/country/province[@name='"
-
+ selProvince + "']"); //获取指定name属性的省份节点
-
List<Element> cityList = item.elements("city");// 获取表示地级市的节点集合
-
Element cityElement = null;
-
for (int i = 0; i < cityList.size(); i++) {
-
cityElement = cityList.get(i);
-
result = result + cityElement.attributeValue("name") + ",";
-
}
-
result = result.substring(0, result.length() - 1); // 去除最后一个逗号
-
} catch (DocumentException e) {
-
e.printStackTrace();
-
}
-
}
-
response.setContentType("text/html");
-
PrintWriter out = response.getWriter();
-
out.print(result); // 输出获取的地级市字符串
-
out.flush();
-
out.close();
-
}
(11)在index.jsp页面中,编写获取地级市信息的Ajax回调函数callback_getCity()。在该函数中,首先将获取的市县名称字符串分隔为数组,然后清空地级市下拉列表(防止下拉列表框的内容重复添加),再通过循环将数组中的市县名称添加到下拉列表中,最后当下拉列表的第一个选项不为空时,还需要调用获取县/县级市/区的方法,获取默认的县、县级市和区信息。callback_getCity ()函数的具体代码如下:
-
function callback_getCity(){
-
if(request.readyState == 4){
-
if(request.status == 200){
-
cityArr=request.responseText.split(","); //将获取的市县名称字符串分隔为数组
-
document.getElementById("city").length=0; //清空下拉列表
-
for(i=0;i<cityArr.length;i++){ //循环将地级市名称添加到下拉列表中
-
document.getElementById("city").options[i]=new Option(cityArr[i],cityArr[i]);
-
}
-
if(cityArr[0]!=""){
-
getArea(document.getElementById("province").value,cityArr[0]);
-
}
-
request = false;
-
}
-
}
-
}
(12)在县/直辖市下拉列表框的onChange事件中,调用getCity()方法,获取地级市信息,关键代码如下:
-
<select name="province" id="province" onChange="getCity(this.value)"></select>
(13)在index.jsp页面中,编写实例化Ajax对象的方法getArea(),用于向服务器发送请求,获取县、县级市和区信息。关键代码如下:
-
function getArea(selProvince,selCity){
-
var url="ZoneServlet";//服务器地址
-
var param ="action=getArea&parProvince="+selProvince+"&parCity="+selCity+"&nocache="
-
+new Date().getTime();
-
request=httpRequest("post",url,true,callback_getArea,param); //调用请求方法
-
}
(14)在ZoneServlet类中,编写获取县、县级市获区的getArea()方法。具体代码如下:
-
public void getArea(HttpServletRequest request, HttpServletResponse response)
-
throws ServletException, IOException {
-
response.setCharacterEncoding("UTF-8"); // 设置响应的编码方式
-
String fileURL = request.getRealPath("/xml/zone.xml"); // 获取XML文件的路径
-
File file = new File(fileURL);
-
Document document = null; // 声明Document对象
-
String result = "";
-
if (file.exists()) { //如果文件存在,则读取该文件
-
SAXReader reader = new SAXReader(); // 实例化SAXReader对象
-
try {
-
document = reader.read(new File(fileURL)); //获取 XML文档对象
-
Element country = document.getRootElement(); // 获取根节点
-
String selProvince = request.getParameter("parProvince"); // 获取选择的省份
-
String selCity = request.getParameter("parCity");//获取选择的地级市
-
selProvince = java.net.URLDecoder.decode(selProvince,"UTF-8");
-
selCity = java.net.URLDecoder.decode(selCity,"UTF-8");
-
Element item = (Element) country.selectSingleNode("/country/province[@name='"
-
+ selProvince + "']");
-
List<Element> cityList = item.elements("city"); // 获取表示地级市的节点集合
-
//获取指定的地级市节点
-
Element itemArea = (Element) item.selectSingleNode("city[@name='" + selCity + "']");
-
result = itemArea.attributeValue("area"); //获取县、县级市或区
-
} catch (DocumentException e) {
-
e.printStackTrace();
-
}
-
}
-
response.setContentType("text/html");
-
PrintWriter out = response.getWriter();
-
out.print(result); // 输出获取的县、县级市或区字符串
-
out.flush();
-
out.close();
-
}
(15)在index.jsp页面中,编写回调函数callback_getArea()。在该函数中,首先将获取的县、县级市和区名称字符串分隔为数组,然后清空县/县级市/区下拉列表(防止下拉列表框的内容重复添加),最后通过循环将数组中的县、县级市和区名称添加到下拉列表中。callback_getCity()函数的具体代码如下:
-
function callback_getArea(){
-
if(request.readyState == 4){
-
if(request.status == 200){
-
areaArr=request.responseText.split(","); //将获取的市县名称字符串分隔为数组
-
document.getElementById("area").length=0; //清空下拉列表
-
for(i=0;i<areaArr.length;i++){ //循环将县、县级市和区名称添加到下拉列表中
-
document.getElementById("area").options[i]=new Option(areaArr[i],areaArr[i]);
-
}
-
request =false;
-
}
-
}
-
}
(16)在地级市下拉列表框的onChange事件中,调用getArea()方法,获取县、县级市和区信息,关键代码如下:
-
<select name="city" id="city"
-
onChange="getArea(document.getElementById('province').value,this.value)">
-
</select>
秘笈心法
本实例在重新为地级市下拉列表和县/县级市/区下拉列表添加选项时,首先需要清空该下拉列表,否则下拉列表的选项值会出现错误。
例如,清空地级市下拉列表可以使用下面的代码:
document.getElementById("city").length=0; //清空下拉列表
清空县/县级市/区下拉列表可以使用下面的代码:
document.getElementById("area").length=0; //清空下拉列表