Ajax实现动态的二级级联菜单
今天花了点时间用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:
1 package cascade.servlet; 2 import java.io.IOException; 3 import java.util.List; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import cascade.entity.District; 11 import cascade.service.DistrictService; 12 13 14 public class InitServlet extends HttpServlet{ 15 16 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 17 doPost(req, resp); 18 } 19 20 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 21 req.setCharacterEncoding("UTF-8"); 22 resp.setCharacterEncoding("UTF-8"); 23 /* 24 * DistrictService ds为操作数据库的对象. 25 * 调用该对象的getAllDistrict()方法,可以从数据库中取得所有的区域信息,封装为List<District>对象,并返回。 26 * 其中District是数据库District表的实体类 27 * 为了把重点放在Ajax和级联菜单的实现上,本文省略了操作数据库的代码!!! 28 */ 29 DistrictService ds = new DistrictService(); 30 List<District> districts = ds.getAllDistrict(); 31 //List<District>对象存在request范围中,并转向到主页 32 req.setAttribute("districts", districts); 33 req.getRequestDispatcher("index.jsp").forward(req, resp); 34 } 35 36 }
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:
1 //XMLHttpRequest组件 2 var xhs; 3 //区域菜单的值发生改变时调用该方法,并把区域菜单当前的value传递进来 4 function cascade(id){ 5 //当id不大于0时,说明当前选择的是“请选择”这一项,则不做操作 6 if(id>0){ 7 //请求字符串,把区域的id作为页面参数传到后台 8 var url="cascade?id="+id; 9 //创建XMLHttpRequest组件 10 xhs=new XMLHttpRequest(); 11 //设置回调函数,processReuqest方法的定义在下面 12 xhs.onreadystatechange=processReuqest; 13 //打开与服务器的地址连接 14 xhs.open("post", url, true); 15 //发送请求 16 xhs.send(null); 17 } 18 } 19 20 //processReuqest方法作为回调方法 21 function processReuqest(){ 22 if(xhs.readyState==4){ 23 if(xhs.status==200){ 24 //创建新的select节点 25 var newSelect=document.createElement("select"); 26 newSelect.id="street"; 27 //为新创建的select节点添加onchange事件,以便测试用 28 newSelect.onchange=function test(){ 29 alert(this.value); 30 }; 31 //为新创建的select节点添加option节点 32 var op=document.createElement("option"); 33 op.value=-1; 34 op.innerHTML="请选择"; 35 newSelect.appendChild(op); 36 //得到完成请求后返回的字串符 37 var str = xhs.responseText; 38 //根据返回的字符串为新创建的select节点添加option节点 39 var arr1=str.split(","); 40 for(var i=0;i<arr1.length;i++){ 41 var arr2=arr1[i].split("="); 42 var child=document.createElement("option"); 43 child.innerHTML=arr2[1]; 44 child.value=arr2[0]; 45 newSelect.appendChild(child); 46 } 47 //用新select节点替换旧的select节点 48 var select = document.getElementById("street"); 49 document.body.replaceChild(newSelect, select); 50 } 51 } 52 }
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:
1 package cascade.servlet; 2 import java.io.IOException; 3 import java.io.PrintWriter; 4 import java.util.List; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 import cascade.entity.District; 12 import cascade.entity.Street; 13 import cascade.service.StreetService; 14 15 16 public class CascadeServlet extends HttpServlet{ 17 18 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 19 doPost(req, resp); 20 } 21 22 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 23 req.setCharacterEncoding("UTF-8"); 24 resp.setCharacterEncoding("UTF-8"); 25 int id =Integer.parseInt(req.getParameter("id")); 26 District district=new District(); 27 district.setId(id); 28 29 /* 30 * StreetService ss为操作数据库的对象. 31 * 调用该对象的getAllStreet()方法,可以从数据库中取得所有的区域信息,封装为List<Street>对象,并返回。 32 * 其中Street是数据库Street表的实体类 33 * 为了把重点放在Ajax和级联菜单的实现上,本文省略了操作数据库的代码!!! 34 */ 35 StreetService ss=new StreetService(); 36 List<Street> streets=ss.getAllStreet(district); 37 //把得到的街道对象集合拼接成字符串文本 38 StringBuffer sb=new StringBuffer(); 39 for(int i=0;i<streets.size();i++){ 40 sb.append(streets.get(i).getId()).append("=").append(streets.get(i).getName()); 41 if(i!=streets.size()-1){ 42 sb.append(","); 43 } 44 } 45 //servlet不转向或重定向到任何页面,使用resp.getWriter().print()方法可以把文本响应给XMLHttpRequest对象 46 PrintWriter out = resp.getWriter(); 47 out.print(sb.toString()); 48 out.flush(); 49 out.close(); 50 } 51 52 }
在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>
完工,效果如下: