今天花了点时间用Ajax实现了一个二级级联菜单。整理总结一下。为了把重点放在Ajax和级联菜单的实现上,本文省略了数据库建表语句和操作数据库的代码!
数据库建表语句就不帖出来了。主要有两张表,区域表:district。街道表:street。区域表和街道表是一对多关系,一个区域可以有零到多个街道,一条街道属于一个区域。数据如下:
页面代码 index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="js/js.js"></script>
<title>Insert title here</title>
</head>
<body>
<select id="district" onchange="cascade(this.value)" >
<option value="-1">请选择</option>
<c:forEach items="${districts }" var="district">
<option value="${district.id }">${district.name }</option>
</c:forEach>
</select>
<select id="street" onchange="alert(this.value)">
<option>请选择</option>
</select>
</body>
</html>
初始化主页面一级菜单列表的Servlet代码 InitServlet.java:
package cascade.servlet;
import java.io.IOException;
import java.util.List; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import cascade.entity.District;
import cascade.service.DistrictService; public class InitServlet extends HttpServlet{ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
} protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
/*
* DistrictService ds为操作数据库的对象.
* 调用该对象的getAllDistrict()方法,可以从数据库中取得所有的区域信息,封装为List<District>对象,并返回。
* 其中District是数据库District表的实体类
* 为了把重点放在Ajax和级联菜单的实现上,本文省略了操作数据库的代码!!!
*/
DistrictService ds = new DistrictService();
List<District> districts = ds.getAllDistrict();
//List<District>对象存在request范围中,并转向到主页
req.setAttribute("districts", districts);
req.getRequestDispatcher("index.jsp").forward(req, resp);
} }
web配置文件 web.xml:
在web.xml中添加如配置代码
<servlet>
<servlet-name>init</servlet-name>
<servlet-class>cascade.servlet.InitServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>init</servlet-name>
<url-pattern>/index</url-pattern>
</servlet-mapping>
此时,在浏览器中访问 InitServlet.java (假设项目名为AjaxCascade,访问地址就是:http://localhost:8080/AjaxCascade/index)
数据库的区域数据就已经填到第一级菜单项中了。
此时页面的源代码可以看到下拉菜单(select)里每一项(option)的value也是从数据库里取到的:
<select id="district" onchange="cascade(this.value)" >
<option value="-1">请选择</option>
<option value="1001">天河区</option>
<option value="1002">越秀区</option>
<option value="1003">海珠区</option>
<option value="1005">番禺区</option>
<option value="1000">白云区</option>
<option value="1004">花都区</option>
</select>
现在开始现实街道下拉菜单(也就是页面上id为street的select)的动态获取。
我们知道,但区域菜单的值改变时,街道菜单的值也要动态的发生改变(如区域选择“天河区”,街道菜单就应该动态加上“中山大道“这一项,如区域选择“海珠区”,街道菜单就应该动态加上“昌岗路”,并删除“中山大道“)。
为了现实上面所说的功能,要为区域菜单添加 onchange 事件的处理:onchange="cascade(this.value)" 当区域菜单值发生改变时调用cascade方法,并把自己的value属性传递进去。
cascade代码如下 js.js:
//XMLHttpRequest组件
var xhs;
//区域菜单的值发生改变时调用该方法,并把区域菜单当前的value传递进来
function cascade(id){
//当id不大于0时,说明当前选择的是“请选择”这一项,则不做操作
if(id>0){
//请求字符串,把区域的id作为页面参数传到后台
var url="cascade?id="+id;
//创建XMLHttpRequest组件
xhs=new XMLHttpRequest();
//设置回调函数,processReuqest方法的定义在下面
xhs.onreadystatechange=processReuqest;
//打开与服务器的地址连接
xhs.open("post", url, true);
//发送请求
xhs.send(null);
}
} //processReuqest方法作为回调方法
function processReuqest(){
if(xhs.readyState==4){
if(xhs.status==200){
//创建新的select节点
var newSelect=document.createElement("select");
newSelect.id="street";
//为新创建的select节点添加onchange事件,以便测试用
newSelect.onchange=function test(){
alert(this.value);
};
//为新创建的select节点添加option节点
var op=document.createElement("option");
op.value=-1;
op.innerHTML="请选择";
newSelect.appendChild(op);
//得到完成请求后返回的字串符
var str = xhs.responseText;
//根据返回的字符串为新创建的select节点添加option节点
var arr1=str.split(",");
for(var i=0;i<arr1.length;i++){
var arr2=arr1[i].split("=");
var child=document.createElement("option");
child.innerHTML=arr2[1];
child.value=arr2[0];
newSelect.appendChild(child);
}
//用新select节点替换旧的select节点
var select = document.getElementById("street");
document.body.replaceChild(newSelect, select);
}
}
}
XMLHttpRequest 对象:XMLHttpRequest 对象提供了对 HTTP 协议的完全的访问,包括做出 POST 和 HEAD 请求以及普通的 GET 请求的能力。XMLHttpRequest 可以同步或异步地返回 Web 服务器的响应,并且能够以文本或者一个 DOM 文档的形式返回内容。尽管名为 XMLHttpRequest,它并不限于和 XML 文档一起使用:它可以接收任何形式的文本文档。XMLHttpRequest 对象是名为 AJAX 的 Web 应用程序架构的一项关键功能。
创建XMLHttpRequest 对象:尽管大部份浏览器都支持 new XMLHttpRequest() 来得到 XMLHttpRequest 对象,但IE浏览器和其他Web浏览器返回的 XMLHttpRequest 对象实例是不相同的,为了支持更多浏览器,建议编码时如下所示的代码创建XMLHttpRequest 实例。
function createXmlHttpRequest(){
if(window.ActiveXObject){
return new ActiveXObject("Microsoft.XMLHTTP");
}else if(window.XMLHttpRequest){
return new XMLHttpRequest();
}
}
open(method,url,async)方法:建立于服务器的连接,method参数指定请求的HTTP方法。URL 参数指定请求的地址。acync 参数指定是否使用异步请求,其值为 true 或 false。
send(content)方法:发送请求,content参数指定请求的参数。当send方法不配置参数,即xhr.send()时,在IE中能够正常运行,但在Firefox中却不能,所以,建议最好加上null 。
onreadystatechange属性:指定XMLHttpRequest对象的回调函数。onreadystatechange属性作用与文本框的onblur等属性一样,是事件处理属性,即当 XMLHttpRequest 的状态发生改变时,XMLHttpRequest 对象都会解发onreadystatechange所指定的函数。
readyState属性:XMLHttpRequest的状态信息。XMLHttpRequest对象有如下几种状态。
0:XMLHttpRequest对象没有完成初始化,此时,已经创建一个XMLHttpRequest对象,但是还没有初始化。
1:XMLHttpRequest对象开始发送请求,此时,代码已经调用open()方法并且XMLHttpRequest已经准备好把一个请求发送到服务器。
2:XMLHttpRequest对象的请求发送完成。此时,已经通过send()方法把一个请求发到服务器端,但是还没有收到一个响应。
3:XMLHttpRequest对象开始读取响应。此时,已经接收到HTTP响应头部信息,但是消息体部分还没有完全接收结束。
4:XMLHttpRequest对象读取响应结束。些时,响应已被完全接收。
status属性:HTTP的状态码,仅当readyState的值为3或4时,status属性才可用。status属性如下。
200:服务器响应正常。
400:无法找到请求的资源。
403:没有访问权限。
404:访问资源不存在。
500:服务器内部错误。
responseText属性:获得响应的文本。当readyState值为0、1、2时,reponseText包含一下空字符串。当readyState值为3时,响应中包含还未完成的响应信息。当readyState值为4时,readyState包含完整的响应信息。
接下来是后台代码的实现,从cascade方法可以看到,XMLHttpRequest对象是请求到名为cascade的地址上的。
CascadeServlet.java:
package cascade.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import cascade.entity.District;
import cascade.entity.Street;
import cascade.service.StreetService; public class CascadeServlet extends HttpServlet{ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
} protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
int id =Integer.parseInt(req.getParameter("id"));
District district=new District();
district.setId(id); /*
* StreetService ss为操作数据库的对象.
* 调用该对象的getAllStreet()方法,可以从数据库中取得所有的区域信息,封装为List<Street>对象,并返回。
* 其中Street是数据库Street表的实体类
* 为了把重点放在Ajax和级联菜单的实现上,本文省略了操作数据库的代码!!!
*/
StreetService ss=new StreetService();
List<Street> streets=ss.getAllStreet(district);
//把得到的街道对象集合拼接成字符串文本
StringBuffer sb=new StringBuffer();
for(int i=0;i<streets.size();i++){
sb.append(streets.get(i).getId()).append("=").append(streets.get(i).getName());
if(i!=streets.size()-1){
sb.append(",");
}
}
//servlet不转向或重定向到任何页面,使用resp.getWriter().print()方法可以把文本响应给XMLHttpRequest对象
PrintWriter out = resp.getWriter();
out.print(sb.toString());
out.flush();
out.close();
} }
在web.xml中的配置:
<servlet>
<servlet-name>cascade</servlet-name>
<servlet-class>cascade.servlet.CascadeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cascade</servlet-name>
<url-pattern>/cascade</url-pattern>
</servlet-mapping>
完工,效果如下: