18.2 AjaxTags入门
前面我们已经说过了:使用Ajax是相当简单的事情,完全不需要烦琐地创建XMLHttpRequest对象来发送Ajax请求。使用AjaxTags甚至不需要编写回调函数,不需要解析服务器响应,不需要程序员手动更新HTML页面显示,整个过程完全由AjaxTags完成,应用开发者只需要编写服务器响应即可。服务器响应就是Ajax请求的处理类。
18.2.1 编写处理类
这里说的处理类并不一定是一个完整的Java类,它可以是一个JSP页面,也可以是一个配置在Web应用中的Servlet或者Struts的Action,甚至是一个非Java的服务器组件,只要它能响应用户的请求即可。当然,因为AjaxTags是一种高度封装的Ajax框架,因此处理类的返回值不能像之前那样随心所欲,而必须满足某种格式。服务器处理类的返回值必须满足如下XML文件格式:
<!-- XML文件的声明部分 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- AjaxTags服务器响应的根元素 -->
<ajax-response>
<!-- AjaxTags服务器响应的内容必须在response里 -->
<response>
<!-- 下面的item节点分别用于不同的选择 -->
<item>
<name>Record 1</name>
<value>1</value>
</item>
<item>
<name>Record 2</name>
<value>2</value>
</item>
<item>
<name>Record 3</name>
<value>3</value>
</item>
</response>
</ajax-response>
当然,也可以使用普通文本响应,使用普通文本响应则应该生成如下格式的响应:
#普通文本响应的示范
#每项响应对应一行,每行的前面部分是name,后面是value
#中间以英文逗号隔开
Record 1,1
Record 2,2
Record 3,3
下面介绍的是一个简单应用,以自动完成为示范建立一个对应的处理类,处理类以JSP来代替。下面是自动完成的处理JSP页面代码。这是一个简单的级联菜单应用,用户一旦选中第一个下拉列表框的某个选项值,下一个下拉列表框将随之变化。处理类由一个JSP页面充当,该页面负责生成一个XML响应,而XML响应则符合上面的格式。下面是该处理器页面的代码:
<%@ page contentType="text/html; charset=GBK" language="java" %>
<%@ page import="java.util.*"%>
<%
//获取请求参数
int country = Integer.parseInt(request.getParameter("country"));
//设置响应的页面头
response.setContentType("text/xml; charset=UTF-8");
//控制响应不会在客户端缓存
response.setHeader("Cache-Control", "no-cache");
//用于控制服务器的响应
List<String> cityList = new ArrayList<String>();
//根据请求参数country来控制服务器响应
switch(country)
{
//对于选择下拉框的“中国”选项
case 1:
cityList.add("广州");
cityList.add("深圳");
cityList.add("上海");
break;
//对于选择下拉框的“美国”选项
case 2:
cityList.add("华盛顿");
cityList.add("纽约");
cityList.add("加州");
break;
//对于选择下拉框的“日本”选项
case 3:
cityList.add("东京");
cityList.add("大阪");
cityList.add("福冈");
break;
}
%>
<ajax-response>
<response>
<%
//遍历集合,依次将城市添加到服务器响应的item项里
for(String city : cityList)
{
%>
<item>
<name><%=city%></name>
<value><%=city%></value>
</item>
<%}%>
</response>
</ajax-response>
该页面根据请求参数,依次将3个城市添加到cityList集合里,然后通过如下代码表示生成的页面是一个XML文件:
response.setContentType("text/xml; charset=UTF-8");
一旦生成了XML响应,就可以在客户端JSP页面使用标签来生成Ajax响应了。
18.2.2 使用标签
在客户端页面使用AjaxTags标签是非常简单而且“非常Java”的,几乎感觉不到使用了Ajax功能,但该页面已经具有了Ajax能力。在JSP页面使用AjaxTags应按如下步骤进行:
在JSP页面中使用taglib导入AjaxTags标签库。
在JSP页面中导入AjaxTags必需的JavaScript库。
使用AjaxTags对应的标签。
使用AjaxTags的select标签的JSP页面代码如下:
<!-- 导入AjaxTags标签库 -->
<%@ taglib uri="http://ajaxtags.org/tags/ajax" prefix="ajax" %>
<!-- 设置页面的内容以及对应的编码集 -->
<%@ page contentType="text/html;charset=GBK"%>
<html>
<head>
<title>第一个AjaxTags应用</title>
<!-- 在JSP页面中引入必需的JavaScript库 -->
<script type="text/javascript" src="js/prototype-1.4.0.js"></script>
<script type="text/javascript" src="js/scriptaculous.js"></script>
<script type="text/javascript" src="js/overlibmws.js"></script>
<script type="text/javascript" src="js/ajaxtags.js"></script>
<!-- 在JSP页面中引入必需的CSS样式单 -->
<link type="text/css" rel="stylesheet" href="css/ajaxtags-sample.css" />
</head>
<body>
国家:
<!-- 激发Ajax的源select元素 -->
<select id="country" name="country">
<option value="">选择一个国家</option>
<option value="1">中国</option>
<option value="2">美国</option>
<option value="3">日本</option>
</select>
城市:
<!-- 显示Ajax响应的select元素 -->
<select id="city" name="city">
<option value="">城市列表</option>
</select>
<!-- 使用AjaxTags标签 -->
<ajax:select
baseUrl="res.jsp"
source="country"
target="city"
parameters="country={country}" />
</body>
</html>
在上面的JSP页面中,除了引入几个JavaScript代码库外,完全不需要任何JavaScript代码,丝毫感受不到使用Ajax的痕迹,但因为使用了AjaxTags的select标签(该标签用于生成级联菜单的Ajax应用),该页面也具有了级联菜单的功能。图18.1显示了使用AjaxTags后该页面的级联菜单效果。
图18.1 使用AjaxTags后级联菜单的效果
通过在JSP页面中使用AjaxTags标签,可以看到AjaxTags标签使Ajax应用非常简单。
因为有些浏览器在处理以GET方式发送的请求时存在一些问题,因此这里修改了ajaxtags.js文件中的请求发送方式。关于请求的发送方式,笔者更倾向于使用POST请求,而AjaxTags默认的请求发送方式是GET。只需打开ajaxtags.js文件,将文件中的method: 'get'替换成method: 'post',即可改变AjaxTags的请求发送方式。
18.3 处理类的几种形式
前面已经介绍过了,AjaxTags的处理类并不一定是一个真正的Java类,它可以有很多形式,甚至于可以是一个非Java文件,唯一的要求是该处理类能返回一个满足格式的XML文件。除此之外,AjaxTags还提供了几个特殊的类,这些特殊的类可以简化处理类的实现。
下面依次介绍处理类的几种形式,介绍这几种形式时都以前面介绍的级联菜单应用为基础,应用使用AjaxTags标签的JSP页面没有太大改变,仅改变了ajax:select标签的baseUrl属性——这是因为采用不同处理类时,处理类在服务器端配置的URL不同。
18.3.1 使用普通Servlet生成响应
使用普通Servlet生成响应与前面介绍的JSP响应是类似的,因为JSP的实质就是Servlet,一旦完成了Servlet的配置,便可以将baseURL指定为该Servlet的URL地址,将请求向该Servlet发送,该Servlet负责生成XML响应,从而完成整个Ajax交互。负责响应的Servlet的源代码如下:
//普通Servlet,继承HttpServlet
public class SelectServlet extends javax.servlet.http.HttpServlet
{
//普通Servlet的服务响应方法
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException,IOException
{
//获得请求参数,即国家值
int country = Integer.parseInt(request.getParameter("country"));
//用于控制服务器的响应
List<String> cityList = new ArrayList<String>();
switch(country)
{
//对于选择下拉框的"中国"选项
case 1:
cityList.add("广州");
cityList.add("深圳");
cityList.add("上海");
break;
//对于选择下拉框的"美国"选项
case 2:
cityList.add("华盛顿");
cityList.add("纽约");
cityList.add("加州");
break;
//对于选择下拉框的"日本"选项
case 3:
cityList.add("东京");
cityList.add("大阪");
cityList.add("福冈");
break;
}
//该类用于辅助生成XML响应
AjaxXmlBuilder builder = new AjaxXmlBuilder();
for (String city : cityList )
{
builder = builder.addItem(city, city);
}
System.out.println(builder);
//设置响应的页面头
response.setContentType("text/xml; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println(builder.toString());
}
}
该Servlet的代码与JSP代码非常类似,它们都是先获取请求参数country,并根据请求参数值的不同,生成包含3个城市的List对象。值得注意的是,Servlet的代码并不需要像在JSP页面里一样自己来控制输出<ajax-response>,<response>,<name>和<value>等标签,这一系列的过程是通过AjaxTags提供的一个辅助类完成的,这个辅助类为AjaxXmlBuilder。借助于AjaxXmlBuilder的帮助,程序开发者可以更简单地生成AjaxTags所需格式的响应。关于AjaxXmlBuilder的内容,将在下一部分介绍。
编译该Servlet,在web.xml文件中增加如下配置,之后该Servlet就能处理客户端请求了。在web.xml文件中增加的配置片段如下:
<!-- 配置Servlet -->
<servlet>
<!-- 配置该Servlet的名字 -->
<servlet-name>select</servlet-name>
<!-- 配置该Servlet的实现类 -->
<servlet-class>lee.SelectServlet</servlet-class>
<!-- 配置该Servlet随应用启动而自启动 -->
<load-on-startup>2</load-on-startup>
</servlet>
<!-- 配置Servlet映射的URL -->
<servlet-mapping>
<!-- 配置该Servlet的名字 -->
<servlet-name>select</servlet-name>
<!-- 配置该Servlet映射的URL地址 -->
<url-pattern>/select</url-pattern>
</servlet-mapping>
一旦完成了Servlet的映射,该Servlet就能在select的URL处提供响应了。因此,只需要在使用AjaxTags标签的页面中通过如下代码来使用标签:
<ajax:select
baseUrl="select"
source="country"
target="city"
parameters="country={country}" />
与之前直接使用JSP作为响应对比,修改了ajax:select标签的baseUrl属性,baseUrl属性是Ajax请求发送的服务器URL,此处改为select,即表示向SelectServlet发送请求。AjaxTags标签各属性的详细内容将在后面介绍。
18.3.2 使用AjaxXmlBuilder辅助类
AjaxXmlBuilder是一个工具类,它可以包含一些工具方法,这些工具方法根据字符串或者集合来生成满足AjaxTags要求的响应。借助于AjaxXmlBuilder的帮助,程序开发者无须手动输出<ajax-response>,<response>,<name>和<value>等标签,只需调用AjaxXmlBuilder的响应方法,它就会自动生成这些标签,并将字符串或集合里的对象的值增加到该响应里。AjaxXmlBuilder对象主要包含如下两个方法。
public AjaxXmlBuilder addItem(java.lang.String name, java.lang.String value):每调用一次,为响应增加一个item节点。其中,第一个参数是item节点下name节点的值,第二个参数是item节点下value节点的值。
public addItems(java.util.Collection collection, String nameProperty, String valueProperty):该方法编辑集合collection的值,collection里的元素是对象,该对象必须包含nameProperty和valueProperty两个属性;该方法增加collection的长度个item节点,每个item节点的name节点的值就是集合元素的nameProperty属性的值,而value节点的值就是valueProperty属性的值。
通过这两个方法,可以非常便捷地生成AjaxTags所需格式的响应。看下面的简单代码:
public class Test
{
public static void main(String[] args)
{
//创建一个默认的AjaxXmlBuilder实例
AjaxXmlBuilder builder = new AjaxXmlBuilder();
//采用循环依次为响应添加5个item节点
for (int i = 0; i < 5; i++ )
{
builder.addItem("name值" + i, "value值" + i);
}
//打印出builder本身所生成的XML响应
System.out.println(builder.toString());
}
}
该文件将生成一个满足AjaxTags响应格式的XML文件,该文件的ajax-response和response节点都会默认包含,该文件包含了5个item节点。下面是该程序的打印结果:
<?xml version="1.0" encoding="UTF-8" ?>
<ajax-response>
<response>
<item>
<name>name值0</name>
<value>value值0</value>
</item>
<item>
<name>name值1</name>
<value>value值1</value>
</item>
<item>
<name>name值2</name>
<value>value值2</value>
</item>
<item>
<name>name值3</name>
<value>value值3</value>
</item>
<item>
<name>name值4</name>
<value>value值4</value>
</item>
</response>
</ajax-response>
上面的XML文件是使用AjaxXmlBuilder的addItem生成的。除此之外,还可以使用addItems方法,该方法会自动遍历集合,并将集合元素的指定属性分别添加为item节点下name节点的值和value节点的值。看下面的示例程序:
public class Test2
{
public static void main(String[] args)throws Exception
{
//构造一个集合对象
List<Person> pl = new ArrayList<Person>();
//向集合添加3个元素,每个元素都是Person实例
pl.add(new Person("Jack", "男"));
pl.add(new Person("Rose", "女"));
pl.add(new Person("小强", "男"));
AjaxXmlBuilder builder = new AjaxXmlBuilder();
//为生成的XML响应增加3个item节点
builder.addItems(pl, "name", "gender");
//输出生成的XML响应
System.out.println(builder.toString());
}
}
该代码使用addItems方法为XML响应添加了3个item节点。每个节点都包含name和value子节点,name节点的值为集合元素的指定name属性,而value节点则为集合元素的gender属性——前提是集合元素有name属性和gender属性。集合元素是Person实例,Person实例应该包括这两个属性,下面是Person类的代码:
public class Person
{
//Person类包含name和gender属性
private String name;
private String gender;
//无参数的构造器
public Person()
{
}
//有参数的构造器
public Person(String name, String gender)
{
this.name = name;
this.gender = gender;
}
//name和gender属性的setter方法
public void setName(String name)
{
this.name = name;
}
public void setGender(String gender)
{
this.gender = gender;
}
//name和gender属性的getter方法
public String getName()
{
return (this.name);
}
public String getGender()
{
return (this.gender);
}
}
运行上面的代码,将生成如下输出:
<?xml version="1.0" encoding="UTF-8" ?>
<ajax-response>
<response>
<item>
<name><![CDATA[Jack]]></name>
<value><![CDATA[男]]></value>
</item>
<item>
<name><![CDATA[Rose]]></name>
<value><![CDATA[女]]></value>
</item>
<item>
<name><![CDATA[小强]]></name>
<value><![CDATA[男]]></value>
</item>
</response>
</ajax-response>
通过使用AjaxXmlBuilder的builder.addItems(pl, "name", "gender")方法,该builder将自动遍历pl集合中的元素,每个元素对应一个item节点。集合元素的name属性为每个item节点的name节点值,gender属性为每个item节点的value节点值。
18.3.3 使用BaseAjaxAction生成响应
这是一种更早期的用法,这种用法提供了一个BaseAjaxAction辅助类来负责生成响应。这个类继承了Struts的Action。因此,如果需要使用这种响应,则应该将Struts所需要的JAR文件复制到Web应用中。
当然,使用这种响应有一个最大的好处:如果Web应用的表现层已经使用了Struts作为MVC框架,那么该应用与AjaxTags的整合将变得更加容易。如果Web应用本身不使用Struts作为MVC框架,那么这种响应不是一种理想选择。
BaseAjaxAction是一个抽象类,但该类已经实现了Struts Action的execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)方法,AjaxTags通过实现该方法来提供响应,具体响应则通过回调getXmlContent(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)方法来实现,该方法是一个抽象方法,必须由程序员实现。
程序员实现getXmlContent(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)方法,该方法返回一个满足AjaxTags要求的XML字符串,BaseAjaxAction根据该方法的返回值来生成响应。下面是一个BaseAjaxAction实现类的示范,该示范也能提供上面级联菜单的响应。
//使用BaseAjaxAction响应,应该集成BaseAjaxAction类
public class SelectAction extends BaseAjaxAction
{
//实现抽象方法
public String getXmlContent(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception
{
//获取请求参数
int country = Integer.parseInt(request.getParameter("country"));
List<String> cityList = new ArrayList<String>();
//对于不同的请求参数,向集合中添加不同的城市字符串
switch(country)
{
//对于选择下拉框的"中国"选项
case 1:
cityList.add("广州");
cityList.add("深圳");
cityList.add("上海");
break;
//对于选择下拉框的"美国"选项
case 2:
cityList.add("华盛顿");
cityList.add("纽约");
cityList.add("加州");
break;
//对于选择下拉框的"日本"选项
case 3:
cityList.add("东京");
cityList.add("大阪");
cityList.add("福冈");
break;
}
//该类用于辅助生成XML响应
AjaxXmlBuilder builder = new AjaxXmlBuilder();
for (String city : cityList )
{
builder = builder.addItem(city, city);
}
//返回生成的XML字符串
return builder.toString();
}
}
一旦实现了getXmlContent方法,BaseAjaxAction便可以根据该方法的返回值来生成Ajax响应。
值得注意的是,这个响应是一个Action,并不是普通的Servlet。因此,它不可以直接作为Servlet来提供响应,而必须借助于Struts框架。为了在Web应用中使用Struts框架,应该修改web.xml文件,在文件中载入Struts框架。修改后的web.xml文件的代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<!-- 配置Struts的核心Servlet -->
<servlet>
<!-- 指定Servlet的名字 -->
<servlet-name>action</servlet-name>
<!-- 指定Servlet的实现类 -->
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置Servlet的映射 -->
<servlet-mapping>
<!-- 指定Servlet的名字 -->
<servlet-name>action</servlet-name>
<!-- 指定Servlet的映射的URL -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
经过上面的配置,所有匹配*.do模式的请求都将自动转向Struts框架,由ActionServlet负责处理。ActionServlet根据请求将请求转发到对应的Action处理。
一旦需要使用Struts框架,则少不了Struts的配置文件,本示例应用中仅使用了一个Action,负责生成客户端响应。因此,struts-config.xml文件中仅需要配置一个Action,具体的配置文件如下:
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Struts 配置文件的DTD信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<struts-config>
<action-mappings>
<!-- Struts配置文件的Action配置 -->
<action path="/select" type="lee.SelectAction"/>
</action-mappings>
</struts-config>
与传统的Struts的Action配置不同,因为AjaxTags的标签action无须转发,因此无须配置forward页面。
该配置文件配置了一个/select的Action,该Action能处理从ActionServlet转发来的请求。为了保证用户请求进入Struts框架的处理,用户请求必须使用.do结尾。页面中使用该Action,必须修改请求对应的URL。下面的代码片段是在页面中使用AjaxTags的BaseAjaxAction响应:
<!-- 使用AjaxTags的select标签,baseUrl指定为Struts的Action -->
<ajax:select
baseUrl="select.do"
source="country"
target="city"
parameters="country={country}" />
通过这种方式,AjaxTags可以与Struts以无缝的方式整合起来。
18.3.4 使用BaseAjaxServlet生成响应
这种方式是对使用Servlet作为响应的一种改进,也是AjaxTags脱离Strust、成为独立框架的一个重大突破。通过使用BaseAjaxServlet作为响应,服务器的响应无须使用编写复杂的Servlet输出,也无须依赖于Struts框架。
BaseAjaxServlet是HttpServlet的一个子类,该类可以直接配置在web.xml文件中来响应客户端请求,无须任何框架的支持。BaseAjaxServlet也是一个抽象类,但它已经重写了HttpServlet的doPost(HttpServletRequest request, HttpServletResponse response)和doGet(HttpServletRequest request, HttpServletResponse response)方法,程序员无须实现这两个方法,这两个方法用于对客户端提供响应。与BaseAjaxAction类似的是,BaseAjaxServlet的子类也必须实现一个回调方法,即getXmlContent(HttpServletRequest request, HttpServletResponse response),该方法返回一个XML字符串,该字符串被作为Ajax请求的响应。
//用于响应Ajax请求的Servlet,继承BaseAjaxServlet
public class SelectServlet extends BaseAjaxServlet
{
//重写getXmlContent方法,该方法的返回值将作为Ajax请求的响应
public String getXmlContent(HttpServletRequest request, HttpServletResponse
response) throws Exception
{
//获取请求参数
int country = Integer.parseInt(request.getParameter("country"));
//初始化需要的集合
List<String> cityList = new ArrayList<String>();
//针对不同的请求参数,将不同的城市添加到集合中
switch(country)
{
//对于选择下拉框的"中国"选项
case 1:
cityList.add("广州");
cityList.add("深圳");
cityList.add("上海");
break;
//对于选择下拉框的"美国"选项
case 2:
cityList.add("华盛顿");
cityList.add("纽约");
cityList.add("加洲");
break;
//对于选择下拉框的"日本"选项
case 3:
cityList.add("东京");
cityList.add("大阪");
cityList.add("福冈");
break;
}
//该类用于辅助生成XML响应
AjaxXmlBuilder builder = new AjaxXmlBuilder();
//遍历集合,将集合中的元素添加为item节点的子节点name和value的值
for (String city : cityList )
{
builder = builder.addItem(city, city);
}
//返回一个XML字符串
return builder.toString();
}
}
这个Servlet没有重写doGet和doPost方法,这也正是BaseAjaxServlet的用处,避免了编写烦琐的doGet和doPost方法,而且无须手动获取输出流,只要返回一个XML字符串即可,而BaseAjaxServlet则将getXmlContent方法的返回值作为响应。
在web.xml文件中配置该Servlet与配置普通Servlet并没有区别,下面是在web.xml文件中配置该Servlet的代码片段:
<!-- 配置Servlet -->
<servlet>
<!-- 配置该Servlet的名字 -->
<servlet-name>select</servlet-name>
<!-- 配置该Servlet的实现类 -->
<servlet-class>lee.SelectServlet</servlet-class>
</servlet>
<!-- 配置Servlet映射的URL -->
<servlet-mapping>
<!-- 配置该Servlet的名字 -->
<servlet-name>select</servlet-name>
<!-- 配置该Servlet映射的URL地址 -->
<url-pattern>/select</url-pattern>
</servlet-mapping>
上面的配置文件表示,该Servlet负责响应URL为/servlet的请求。因此,在JSP页面中使用Ajax标签的代码片段与使用普通Servlet作为响应的标签并没有区别,代码片段如下:
<!-- 使用Ajax的select标签 -->
<ajax:select
baseUrl="select"
source="country"
target="city"
parameters="country={country}" />
18.3.5 使用非Java响应
前面已经介绍过了,AjaxTags标签并不一定要求服务器采用Servlet或者JSP作为响应,甚至允许使用非Java响应,例如直接使用静态XML文件作为响应。虽然这种场景的应用不是特别广泛,但也不是完全没有用处,下面介绍使用静态XML文件作为响应的情形。静态XML文件的源代码如下:
<?xml version="1.0" encoding="GBK"?>
<ajax-response>
<response>
<item>
<name>广州</name>
<value>广州</value>
</item>
<item>
<name>深圳</name>
<value>深圳</value>
</item>
<item>
<name>上海</name>
<value>上海</value>
</item>
</response>
</ajax-response>
读者可能已经看出来了:这个静态XML文件是当country请求参数为1时Servlet的响应。如果使用这个静态XML文件作为响应,则不管在客户端选择哪个国家,都将输出中国的3个城市。这并不符合实际情况,这里仅用于测试程序的功能。
假设该文件的文件名为res.xml,并将该文件放置在与select.jsp页面相同的路径下,然后在select.jsp页面中通过如下标签来完成Ajax交互:
<!-- 使用AjaxTags的标签 -->
<ajax:select
baseUrl="res.xml"
source="country"
target="city"
parameters="country={country}" />
Ajax select标签的baseUrl改为res.xml,表示直接向静态的XML文件请求。不管请求参数是什么,请求总是得到相同的响应。图18.2显示了这种情况。
图18.2 使用静态XML文件作为响应
18.4 AjaxTags常用标签的使用
AjaxTags提供的标签并不是特别多,但这些标签都是特别常用的,例如自动完成、级联菜单等。使用AjaxTags的标签才能真正让我们从烦琐的JavaScript处理中抽身而出,以“非常Java”的方式优雅地完成Ajax应用。
18.4.1 使用自动完成标签
自动完成标签的功能类似于Internet Explorer中文本框具有的功能,系统可以根据用户的输入提示自动完成选择。
假设输入城市,如果用户输入“广”,系统会读取数据库,对比数据库的记录,找出所有以“广”开头的城市,例如“广岛”和“广州”,从而提供给用户选择。这种自动完成的Ajax应用在第11章已经介绍过了,但那个自动完成应用大约有200多行的JavaScript代码,下面以AjaxTags来完成这个Ajax应用。自动完成功能使用ajax:autocomplete,这个标签有如下几个属性。
var:这是一个可选属性,该属性定义了autocomplete标签创建的JavaScript对象名。通常无须指定该属性。
attachTo:这是一个可选属性,该属性定义了var对应的自动完成对象将应用到的对象。
baseUrl:这是一个必需属性,该属性定义了Ajax请求发送的URL。该属性指定的URL将返回一个典型的AjaxTags所需要的XML响应,响应的每个item节点的name值就是自动完成的提供给用户选择的一项。该属性支持表达式语言。
source:这是一个必需属性,该属性指定的HTML元素的值一旦发生改变,就将发送Ajax请求。通常,该属性的文本将作为自动完成的前缀部分,即source元素指定的HTML元素里的文本将被作为请求参数,伴随着Ajax请求一同发送,一旦该请求参数发送到服务器的baseUrl,baseUrl处的服务器响应将返回一个满足条件的XML响应。当然,也可以指定其他请求参数。
target:这是一个必需属性,该属性指定一个文本框,该文本框将显示自动完成的选择项对应的value。如果用户无须使用额外的文本框来显示value,则可将该参数设置为与source相同。
parameters:这是一个必需属性,该属性指定了Ajax请求的请求参数。
className:这是一个必需属性,该属性指定了自动完成所提供的下拉框的CSS样式单的名字。通常,系统提供该CSS样式单,但用户也可以自定义自己的 CSS样式单。
indicator:这是一个可选属性,该属性指定一个HTML元素,该元素在Ajax请求开始时出现,随着Ajax交互完成而隐藏。该元素可以通知用户Ajax交互的进度。
minimumCharacters:这是一个可选属性,该属性指定自动完成最少所需的字符数。假设source指定一个文本框,如果minimumCharacters的属性值为2,则至少要求source指定的文本框提供两个字符的输入,系统才会提供自动完成功能。
appendSeparator:这是一个可选属性,一旦设置了该属性,target属性指定的文本框的值就不会被覆盖,而是在后面添加上自动完成的value节点的值,添加时将以appendSeparator属性指定的字符串作为分隔符。
preFunction:这是一个可选属性,该属性指定了Ajax交互之前自动执行的函数。
postFunction:这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
errorFunction:这是一个可选属性,该属性指定服务器响应出错时执行的函数。
parser:这是一个可选属性,该属性指定一个服务器响应的解析器,通常无须指定该解析器,除非用户需要自己完成特别的工作。默认的解析器是ResponseHtmlParser。
为了更好地演示Ajax应用,首先引入了一个JavaBean,这个JavaBean表示一本书,书里包含书名和出版社两个属性。下面是Book类的代码:
//普通的值对象(VO)
public class Book implements Serializable
{
//Book类包含的两个属性
private String name;
private String publisher;
//Book类的构造器
public Book()
{
}
//Book类的构造器,构造时传入两个参数
public Book(String name, String publisher) {
this.name = name;
this.publisher = publisher;
}
//name属性的setter方法
public void setName(String name) {
this.name = name;
}
//publisher属性的setter方法
public void setPublisher(String publisher) {
this.publisher = publisher;
}
//name属性的getter方法
public String getName() {
return (this.name);
}
//publisher属性的getter方法
public String getPublisher() {
return (this.publisher);
}
/**
* 重写的toString方法
* @see java.lang.Object#toString()
*/
public String toString()
{
return new ToStringBuilder(this).append("书名:", name).append("出
版社:", publisher).toString();
}
}
该Book类就是一个普通的值对象(VO),它包装了在页面中显示的Java实例。
为了简单起见,我们不再使用复杂的持久层组件,即不再使用数据库来保存数据,不使用DAO组件来完成数据库访问,而是将所有的数据以静态属性的方式保存在业务逻辑组件中,业务逻辑组件不再需要依赖持久层组件,而是直接访问静态属性,从而提供业务逻辑的实现。考虑到后面的AjaxTags标签,此处的BookService组件包含了多个业务逻辑方法。
下面是BookService类的源代码:
public class BookService
{
//以此静态属性代替数据库保存的数据
static final List<Book> books = new ArrayList<Book>();
//添加数据
static
{
//添加电子工业出版社出版的书籍
books.add(new Book("Spring2.0宝典", "电子工业出版社"));
books.add(new Book("Java入门与精通", "电子工业出版社"));
books.add(new Book("Spring实战", "电子工业出版社"));
books.add(new Book("Hibernate入门与精通", "电子工业出版社"));
books.add(new Book("Java网络编程", "电子工业出版社"));
//添加清华大学出版社出版的书籍
books.add(new Book("软件工程导论", "清华大学出版社"));
books.add(new Book("Java教程", "清华大学出版社"));
books.add(new Book("Hibernate持久化", "清华大学出版社"));
books.add(new Book("Java动画设计", "清华大学出版社"));
//添加机械工业出版社出版的书籍
books.add(new Book("软件工程的艺术", "机械工业出版社"));
books.add(new Book("Java高级程序设计", "机械工业出版社"));
books.add(new Book("Spring项目指南", "机械工业出版社"));
books.add(new Book("Java项目指南", "机械工业出版社"));
}
//构造器
public BookService()
{
}
/**
* 根据出版社查询所出版的书
* @param publisher 出版社
* @return 该出版社所出的全部书籍
*/
public List getBooksByPublisher(String publisher)
{
//作为结果返回的集合对象
List<Book> result = new ArrayList<Book>();
//遍历所有的书,从中选出publisher出版社的书
for ( Book book : books)
{
if (book.getPublisher().equalsIgnoreCase(publisher))
{
result.add(book);
}
}
return result;
}
/**
* 根据书名前缀返回以该前缀开始的全部书籍
* @param prefix 书名前缀
* @return 所有以prefix开头的书籍
*/
public List<Book> getBooksByPrefix(String prefix)
{
//作为结果返回的集合对象
List<Book> result = new ArrayList<Book>();
//遍历所有的书,从中选出所有以prefix开头的书籍
for (Book book : books)
{
if (book.getName().toLowerCase().startsWith(prefix.toLowerCase()))
{
result.add(book);
}
}
return result;
}
/**
* 返回全部书籍
* @return 所有书籍
*/
public List<Book> getAllBooks()
{
return books;
}
}
这个BookService业务逻辑组件将作为本节所有示例程序的业务逻辑组件。本示例程序在添加书籍实例时,并未考虑书籍的真实性,只是随便添加,作为应用示例使用,请读者不要误会。本业务逻辑组件将所有的持久层数据作为组件属性保存,并未真正从数据库读取。下面是自动完成的Servlet,该Servlet基于BaseAjaxServlet完成,其代码如下:
//自动完成的Servlet,继承BaseAjaxServlet
public class AutocompleteServlet extends BaseAjaxServlet
{
//重写getXmlContent方法,该方法的返回值作为Ajax请求的响应
public String getXmlContent(HttpServletRequest request, HttpServletResponse
response) throws Exception
{
//获得请求参数
String prefix = request.getParameter("prefix");
System.out.println("xxx" + prefix);
//创建业务逻辑组件的实例
BookService service = new BookService();
//返回以特定前缀开始的所有书籍
List list = service.getBooksByPrefix(prefix);
//借助于AjaxXmlBuilder返回XML字符串
AjaxXmlBuilder builder = new AjaxXmlBuilder();
try
{
builder = builder.addItems(list, "name", "publisher");
return builder.toString();
}
catch (Throwable e)
{
e.printStackTrace();
}
return null;
}
}
该Servlet根据发送的请求参数调用业务逻辑组件方法,从所有的书籍中选择出所有书名以prefix开头的书籍。将该Servlet配置在web.xml文件中,该Servlet即能对Ajax请求提供响应。在web.xml文件中增加如下片段来完成Servlet的配置:
<!-- 配置Servlet -->
<servlet>
<!-- Servlet的名字 -->
<servlet-name>autocomplete</servlet-name>
<!-- Servlet的实现类 -->
<servlet-class>lee.AutocompleteServlet</servlet-class>
</servlet>
<!-- 配置Servlet的映射 -->
<servlet-mapping>
<!-- Servlet的名字 -->
<servlet-name>autocomplete</servlet-name>
<!-- Servlet映射的URL -->
<url-pattern>/autocomplete</url-pattern>
</servlet-mapping>
一旦完成了该Servlet的映射,即可在页面中使用autocomplete标签,该标签提供了自动完成功能。下面是在页面中使用自动完成标签的代码片段:
<ajax:autocomplete
//根据书名文本框的值改变来发送Ajax请求
source="name"
//将value节点的值输入publisher文本框
target="publisher"
//Ajax请求的发送地址
baseUrl="autocomplete"
//自动完成的提示框的CSS样式单
className="autocomplete"
//请求参数,其中{name}是表达式,输出name表单域的值
parameters="prefix={name}"
//当Ajax交互时显示indicator
indicator="indicator"
//至少需要两个字符才发送Ajax请求
minimumCharacters="2"
/>
图18.3显示了自动完成的示范效果。
图18.3 自动完成示范
一旦用户选择了相应的书籍,该书籍的出版社将自动填写在publisher文本框内。
18.4.2 使用area标签
该标签允许在页面中开辟出一个单独区域,而该区域的超链接请求等不会刷新整个页面,而是仅刷新部分内容。该标签有如下几个属性。
id:这是一个必需属性,该属性指定area的ID,用于唯一标识该area,该属性值将成为area对应的DIV元素的ID属性值。
ajaxFlag:这是一个可选属性,该属性指定该页面的其他部分是否忽略Ajax请求。
style:这是一个可选属性, 用于指定内联的CSS样式。
styleClass:这是一个可选属性,用于指定CSS样式单名。
ajaxAnchors:这是一个可选属性。
下面是一个使用area标签的示例代码片段:
<!-- 定义一个页面范围内的Java实例 -->
<jsp:useBean id="now" class="java.util.Date"/>
<!-- 使用表达式输出页面内的Java实例 -->
日期:${now}<p>
<!-- 使用ajaxl:area标签 -->
<ajax:area id="myarea" style="background:#eeeeee; width:300px; height:80px;"
ajaxAnchors="true" ajaxFlag="myarea">
简单的页面area
<br/>
<a href="pagearea.jsp">单击此处</a>
<br/>
<!-- 使用表达式输出页面内的Java实例 -->
日期: ${now}
</ajax:area>
页面中分别有两次输出当前时间的代码,有两次输出now变量的代码。因为后一个日期的输出放在ajax:area标签内,单击“单击此处”超链接时,页面刷新,但不会刷新整个页面,仅刷新ajax:area标签内的部分内容。如果单击了“单击此处”超链接,那么可看到第二个日期发生了改变,但第一个日期不会被刷新。图18.4显示了ajax:area的局部刷新效果。
图18.4 ajax:area的局部刷新
18.4.3 使用anchors标签
这是一个超链接标签,但该超链接标签与普通的超链接不同,该超链接标签仅刷新页面的局部——刷新ajax:area标签指定的页面部分。因此,该标签必须与ajax:area一起使用。该标签有如下两个属性。
target:这是一个必需属性,该属性指定刷新的ajax:area元素的ID属性。
ajaxFlag:这是一个可选属性,该属性指定页面的其他部分是否忽略Ajax请求。
使用ajax:anchors标签,可以通过页面中的超链接而不是ajax:area中的超链接来控制局部刷新。
<jsp:useBean id="now" class="java.util.Date"/>
日期:${now}<p>
<ajax:anchors target="myarea" ajaxFlag="myarea"><a href="anchors.jsp">
单击此处</a></ajax:anchors>
<ajax:area id="myarea" style="background:#eeeeee; width:300px; height:80px;"
ajaxAnchors="true" ajaxFlag="myarea">
简单的页面area
<br/>
<a href="anchors.jsp">单击此处</a>
<br/>
日期: ${now}
</ajax:area>
在上面的页面中有两个超链接,一个是页面范围的超链接,一个是在ajax:area范围内的超链接。两个超链接都可以控制页面的局部刷新。
18.4.4 使用callout标签
该标签是一个服务器提示功能,这个功能以前通常在客户端完成,当用户的鼠标移动到某个产品上面时,该产品上将出现一个提示框,但这个提示框的信息往往是写在客户端的。通过使用callout标签,可以让服务器响应作为提示框的信息。从AjaxTags 1.2以后,该功能的实现依赖于OverLIBMWS JavaScript库,因此应将其JavaScript库复制到对应的Web应用中。该标签支持如下几个属性。
var:这是一个可选属性,该属性定义了autocomplete标签创建的JavaScript对象名。通常无须指定该属性。
attachTo:这是一个可选属性,该属性定义了var对应的自动完成对象将应用到的对象。
baseUrl:这是一个必需属性,该属性定义了Ajax请求发送的URL。该属性指定的URL将返回一个典型的AjaxTags所需要的XML响应,响应的每个item节点的name值就是自动完成功能提供给用户选择的一项。该属性支持表达式语言。
source:该属性指定哪个HTML元素将触发服务器提示框,即指定哪个HTML元素触发Ajax请求。必须指定source和sourceClass两个属性之一。
sourceClass:该属性指定一类HTML元素将触发服务器提示框,即指定哪些HTML元素是该CSS样式单,这些HTML元素都可以触发Ajax请求。必须指定source和sourceClass两个属性之一。
parameters:伴随Ajax请求发送的请求参数。该属性的值支持一个特殊的变量ajaxParameter,该变量代表发送请求的内容。
title:这是一个可选属性,该属性指定信息提示框的标题。
overlib:这是一个可选属性,该属性指定OverLib库的各种选项。
preFunction:这是一个可选属性,该属性指定了Ajax交互之前自动执行的函数。
postFunction:这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
errorFunction:这是一个可选属性,该属性指定服务器响应出错时执行的函数。
callout标签所需要的XML响应只需要一个item节点,该节点的name节点值将作为提示的标题显示,而value节点值将作为提示的内容显示。下面是示例应用的Servlet:
//作为AjaxTags响应的Servlet
public class CalloutServlet extends BaseAjaxServlet
{
//重写getXmlContent方法
public String getXmlContent(HttpServletRequest request, HttpServletResponse
response) throws Exception
{
//Ajax请求总以utf-8编码集发送
request.setCharacterEncoding("UTF-8");
//获取请求参数
String param = request.getParameter("book");
System.out.println(param);
//第一个参数是name节点的值,作为提示的标题
//第二个参数是value节点的值,作为提示的内容
AjaxXmlBuilder builder = new AjaxXmlBuilder().addItemAsCData(
"提示标题",
"<p>关于书籍:<b>" + param + "</b>的信息如下:<br>" +
"服务器的提示信息 </p>");
return builder.toString();
}
}
将该Servlet配置在应用中,为了配置该Servlet,在web.xml文件中增加如下片段:
<!-- 配置Servlet -->
<servlet>
<!-- Servlet的名字 -->
<servlet-name>callout</servlet-name>
<!-- Servlet的实现类 -->
<servlet-class>lee. CalloutServlet</servlet-class>
</servlet>
<!-- 配置Servlet的映射 -->
<servlet-mapping>
<!-- Servlet的名字 -->
<servlet-name> callout </servlet-name>
<!-- Servlet映射的URL -->
<url-pattern>/ callout </url-pattern>
</servlet-mapping>
该Servlet即可响应用户的Ajax请求,下面是页面中使用callout标签的代码片段:
<div style="font-size: 90%; width: 650px; border: 1px dashed #999; padding: 10px">
<p>
下面是目前J2EE领域内容最丰富的三本书:<p>
<!-- 下面3个HTML元素的class属性为book -->
<li><a href="javascript:void(0)" class="book">Spring2.0宝典</a>
<li><a href="javascript:void(0)" class="book">轻量级J2EE企业开发实战</a>
<li><a href="javascript:void(0)" class="book">Ajax开发宝典</a>
</p>
</div>
<!-- 使用callout标签 -->
<ajax:callout
//Ajax请求发送的URL
baseUrl="callout"
//所有class属性为book的HTML元素都将触发Ajax请求
sourceClass="book"
//请求参数
parameters="book={ajaxParameter}"
title="书籍详细信息"
/>
读者应该看到parameters属性的值为book={ajaxParameter},其中,ajaxParameter是一个特殊的变量,这个变量代表任何发送请求的HTML元素本身。当“Spring2.0宝典”发送请求时,该变量的值就是“Spring2.0宝典”。图18.5显示了这种服务器提示的效果。
图18.5 服务器提示
18.4.5 使用htmlContent标签
该标签能将一个HTML页面的内容显示在当前页面的某个区域内(通常是一个DIV元素)。该标签不再需要XML响应,它只需要一个标准的HTML响应,这个HTML响应将直接输出页面的目标容器。该标签有如下几个属性。
var:这是一个可选属性,该属性定义了autocomplete标签创建的JavaScript对象名。通常无须指定该属性。
attachTo:这是一个可选属性,该属性定义了var对应的自动完成对象将应用到的对象。
baseUrl:这是一个必需属性,该属性指定了Ajax请求发送的URL。
source:该属性指定哪个HTML元素将触发服务器提示框,即指定哪个HTML元素触发Ajax请求。必须指定source和sourceClass两个属性之一。
sourceClass:该属性指定一类HTML元素将触发服务器提示框,即指定哪些HTML元素是该CSS样式单,这些HTML元素都可以触发Ajax请求。必须指定source和sourceClass两个属性之一。
target:这是一个必需属性,该属性指定了HTML响应的输出容器。
parameters:这是一个可选属性,如果需要发送请求参数,则需要使用该属性。
preFunction:这是一个可选属性,该属性指定了Ajax交互之前自动执行的函数。
postFunction:这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
errorFunction:这是一个可选属性,该属性指定服务器响应出错时执行的函数。
下面是提供了htmlContent响应的Servlet,该Servlet不再生成XML响应,而是生成HTML响应。
public class HtmlContentServlet extends BaseAjaxServlet
{
//重写getXmlContent方法,生成服务器响应
public String getXmlContent(HttpServletRequest request, HttpServletResponse
response) throws Exception
{
//设置服务器解码方式
request.setCharacterEncoding("UTF-8");
//获取请求参数
String publisher = request.getParameter("publisher");
//创建业务逻辑组件实例
BookService service = new BookService();
//根据出版社获取所有的书籍
List<Book> list = service.getBooksByPublisher(publisher);
//开始拼接返回的字符串
StringBuffer html = new StringBuffer();
html.append("<h3>").append(publisher).append("出版的书籍包括如下</h3>");
for (Book book: list)
{
html.append("<li>").append(book.getName()).append("</li>");
}
html.append("</ul>");
html.append("<br>");
html.append("最后更新时间:" + new Date());
return html.toString();
}
}
正如在代码中看到的,该Servlet不再借助于AjaxXmlBuilder类来辅助生成XML响应,该Servlet不再返回一个XML文件,而是返回一个HTML文档。htmlContent会将该响应直接输出在HTML文档的目标元素中。将该Servlet配置在Web应用中,在web.xml文件中增加如下片段:
<servlet>
<servlet-name>htmlContent</servlet-name>
<servlet-class>lee.HtmlContentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>htmlContent</servlet-name>
<url-pattern>/htmlContent</url-pattern>
</servlet-mapping>
在页面中使用ajax:htmlContent标签,本页面中使用了两种方式来输出htmlContent内容,一种是采用超链接,一种是采用下拉框。代码片段如下:
选择出版社查看详细信息:<br>
<ul>
<!-- 下面使用超链接来生成htmlContent -->
<li><a href="javascript://nop/" class="publisher">电子工业出版社</a></li>
<li><a href="javascript://nop/" class="publisher">清华大学出版社</a></li>
<li><a href="javascript://nop/" class="publisher">机械工业出版社</a></li>
</ul>
<!-- 下面使用下拉列表来生成htmlContent -->
<p>选择出版社查看详细信息:</p>
<select id="publishSelector" name="publishSelector">
<option value="电子工业出版社">电子工业出版社</option>
<option value="清华大学出版社">清华大学出版社</option>
<option value="机械工业出版社">机械工业出版社</option>
</select>
<>
<div id="bookDesc" style="position:absolute;left:300px;top:20px;background-color:
#ffffaa"> </div>
<!-- 第一次使用htmlContent标签 -->
<ajax:htmlContent
baseUrl="htmlContent"
//所有class属性为publisher的HTML元素都将发送Ajax请求
sourceClass="publisher"
//指定输出HTML响应的目标容器
target="bookDesc"
parameters="publisher={ajaxParameter}"
/>
<!-- 第二次使用htmlContent标签 -->
<ajax:htmlContent
baseUrl="htmlContent"
//指定publisherSelector元素发送Ajax请求
source="publishSelector"
//指定输出HTML响应的目标容器
target="bookDesc"
//发送请求参数
parameters="publisher={publishSelector}"
/>
页面中的超链接和下拉框都可以激发HTML内容,一旦用户单击了超链接或者选择了下拉列表,都可以看到该出版社出版的所有图书。图18.6显示了htmlContent标签的效果。
图18.6 使用htmlContent输出HTML响应
18.4.6 使用portlet标签
portlet标签将在页面上生成一个Portlet区域,该区域的内容直接显示服务器端HTML响应。类似于htmlContent标签,该标签不需要 XML响应,而是支持HTML响应。使用ajax:portlet标签还可以定义该Portlet的内容是否支持周期性刷新。该标签包含如下几个属性。
var:这是一个可选属性,该属性定义了autocomplete标签创建的JavaScript对象名。通常无须指定该属性。
attachTo:这是一个可选属性,该属性定义了var对应的自动完成对象将应用到的对象。
baseUrl:这是一个必需属性,该属性指定了Ajax请求发送的URL。
source:这是一个必需属性,该属性指定了Portlet的ID属性值。
parameters:这是一个可选属性,该属性指定发送Ajax请求的请求参数。
classNamePrefix:这是一个可选属性,该属性指定了Portlet的Box,Tools,refresh,Size,Close,Title和Content元素的CSS样式单。
title:这是一个必需属性,该属性指定Portlet的标题。
imageClose:这是一个可选属性,该属性指定关闭按钮的图标。
imageMaximize:这是一个可选属性,该属性指定最大化按钮的图标。
imageMinimize:这是一个可选属性,该属性指定最小化按钮的图标。
imageRefresh:这是一个可选属性,该属性指定刷新按钮的图标。
refreshPeriod:这是一个可选属性,该属性指定Portlet的内容刷新频率,即隔多少秒刷新一次。如果没有指定该属性,则Portlet的内容不会自动刷新,除非手动刷新。默认情况下,当页面加载时,Portlet的内容也会刷新,但如果设置executeOnLoad 为false,则页面载入时,Portlet的内容不会刷新,除非手动刷新。
executeOnLoad:这是一个可选属性,该属性指定当页面重载时,是否重新检索Portlet里的内容,默认是重新检索。
expireDays:这是一个可选属性,该属性指定cookie持久保存的天数。
expireHours:这是一个可选属性,该属性指定cookie持久保存的小时数。
expireMinutes:这是一个可选属性,该属性指定cookie持久保存的分钟数。
preFunction:这是一个可选属性,该属性指定了Ajax交互之前自动执行的函数。
postFunction:这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
errorFunction:这是一个可选属性,该属性指定服务器响应出错时执行的函数。
该标签需要的响应完全类似于htmlContent标签的响应,此处不再单独为该标签编写服务器处理类,而是直接向htmlContent发送Ajax请求,因此可以在页面中直接使用portlet标签。使用portlet标签的代码片段如下:
<ajax:portlet
//指定Portlet的ID 属性
source="tsinghua"
//发送请求的URL
baseUrl='htmlContent'
//Portlet必需的CSS样式
classNamePrefix="portlet"
//Portlet的标题
title="清华大学出版社 Portlet"
//指定几个按钮的图标
imageClose="img/close.png"
imageMaximize="img/maximize.png"
imageMinimize="img/minimize.png"
imageRefresh="img/refresh.png"
//请求参数
parameters="publisher=清华大学出版社"
//每5s刷新一次该Portlet
refreshPeriod="5" />
<ajax:portlet
source="phei"
baseUrl='htmlContent'
classNamePrefix="portlet"
title="电子工业出版社 Portlet"
imageClose="img/close.png"
imageMaximize="img/maximize.png"
imageMinimize="img/minimize.png"
imageRefresh="img/refresh.png"
parameters="publisher=电子工业出版社"
refreshPeriod="5" />
上面的页面使用了两个Portlet,页面执行的效果如图18.7所示,其中清华大学出版社的Portlet已经被最小化了,因此看不到该Portlet的内容。
图18.7 使用portlet标签生成Portlet效果
18.4.7 使用select标签
select标签就是在18.2和18.3节中频繁使用的标签,它的主要作用是实现级联下拉框效果。所谓级联下拉框,就是根据第一个下拉框选择的值,动态改变第二个下拉框的选项。select标签有如下几个属性。
var:这是一个可选属性,该属性定义了autocomplete标签创建的JavaScript对象名。通常无须指定该属性。
attachTo:这是一个可选属性,该属性定义了var对应的自动完成对象将应用到的对象。
baseUrl:这是一个必需属性,该属性指定了Ajax请求发送的URL。
source:这是一个必需属性,该属性指定了第一个下拉框的ID属性,当该属性指定的下拉框改变时触发Ajax交互。
target:这是一个必需属性,该属性指定了第二个下拉框的ID属性,当Ajax响应完成后,AjaxTags会根据响应来自动更新该属性指定的下拉框。
parameters:这是一个可选属性,如果需要发送请求参数,则需要使用该属性。
eventType:这是一个可选属性,该属性指定了源对象上触发请求的事件类型。
executeOnLoad:这是一个可选属性。
defaultOptions:这是一个可选属性,该属性是一系列以逗号隔开的值,这些值将总是作为第二个下拉框的默认选项。
preFunction:这是一个可选属性,该属性指定了Ajax交互之前自动执行的函数。
postFunction:这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
errorFunction:这是一个可选属性,该属性指定服务器响应出错时执行的函数。
parser:这是一个可选属性,该属性指定一个服务器响应的解析器。通常无须指定该解析器,除非用户需要自己完成特别的工作。默认的解析器是ResponseHtmlParser。
因为在18.2和18.3节中已经大量使用了该标签,因此此处不再给出关于它的示范应用。值得注意的是,select标签只能有一个源下拉框和一个目标下拉框,这往往不能满足实际需要。
例如,对于一个常用场景:第一个下拉框是国家,第二个下拉框是省份,第三个下拉框是城市。每个下拉框的值都应该随前面下拉框值的改变而改变。AjaxTags的select也可以满足这个需要,只要使用两个select标签即可。
对于第一个select标签,国家下拉框是源下拉框,省份下拉框是目标下拉框;对于第二个select标签,省份下拉框是源下拉框,城市下拉框是目标下拉框。
使用select标签可以很方便地实现多级联动下拉框。
18.4.8 创建Tab页
Tab页的创建依赖于两个标签:tabPanel和tab。tabPanel是一个整体的Tab效果,而每个tab则是该Tab效果里的每个tab页。因此,tabPanel和tab两个标签通常一起使用。tabPanel标签包含如下几个属性。
var:这是一个可选属性,该属性定义了autocomplete标签创建的JavaScript对象名。通常无须指定该属性。
attachTo:这是一个可选属性,该属性定义了var对应的自动完成对象将应用到的对象。
panelStyleId:这是一个必需属性,指定Tab页的ID属性值。
contentStyleId:这是一个必需属性,指定Tab页面内容的ID属性值。
panelStyleClass:这是一个可选属性,该属性指定Tab页整体使用的CSS样式单。
contentStyleClass:这是一个可选属性,该属性指定Tab页内容所使用的CSS样式单。
currentStyleClass:这是一个必需属性,该属性指定了激活的Tab页所使用的CSS样式单。
preFunction:这是一个可选属性,该属性指定了Ajax交互之前自动执行的函数。
postFunction :这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
errorFunction:这是一个可选属性,该属性指定服务器响应出错时执行的函数。
parser:这是一个可选属性,该属性指定一个服务器响应的解析器。通常无须指定该解析器,除非用户需要自己完成特别的工作。默认的解析器是ResponseHtmlParser。
tab标签包含如下几个属性。
baseUrl:这是一个必需属性,该属性指定了Ajax请求发送的URL。
caption:这是一个必需属性,该属性指定了Tab页的标题。
defaultTab:这是一个可选属性,该属性指定页面是否作为Tab效果的初始页。
parameters:这是一个可选属性,如果加载页面时需要发送请求参数,则需要使用该属性。
值得注意的是,tab标签的Ajax响应无须使用XML响应,而应该使用标准的HTML响应,tab标签将该HTML内容直接输出在Tab页中。
下面的应用示范一个简单的Tab效果,每个Tab页面的baseUrl都使用前面htmlContent中已经定义了的htmlContent Servlet。下面是使用tabPanel和tab标签的代码片段:
<!-- 使用tabPanel构建整体的Tab效果 -->
<ajax:tabPanel
panelStyleId="tabPanel"
contentStyleId="tabContent"
panelStyleClass="tabPanel"
contentStyleClass="tabContent"
currentStyleClass="ajaxCurrentTab">
<!-- tabPanel的每个tab子标签对应一个Tab页 -->
<ajax:tab caption="电子工业出版社"
baseUrl="htmlContent"
parameters="publisher=电子工业出版社"
defaultTab="true"/>
<ajax:tab caption="清华大学出版社"
baseUrl="htmlContent"
parameters="publisher=清华大学出版社"/>
<ajax:tab caption="机械工业出版社"
baseUrl="htmlContent"
parameters="publisher=机械工业出版社"/>
</ajax:tabPanel>
上面的代码将ajax:tab标签放在ajax:tabPanel内,从而形成一个整体的Tab效果,每个tab标签对应一个基本的Tab页,每个Tab页所显示的URL完全相同,请求参数不同,每个Tab页的内容也不相同。图18.8显示了该Tab效果。
图18.8 Tab效果
18.4.9 使用displayTag标签
这个标签需要依赖于Apache组织下的DisplayTag项目,AjaxTags封装了DisplayTag项目,但增加了Ajax引擎,以便能以Ajax的方式来排序、分页。该标签的核心是DisplayTags项目,读者应该具有DisplayTags的相关知识。AjaxTags中的displayTag标签有如下几个属性。
id:这是一个必需属性,该属性指定了displayTag对应的DIV元素的ID属性值。
ajaxFlag:这是一个可选属性,该属性指定该页面的其他部分是否忽略Ajax请求。
style:内联CSS样式单属性。
styleClass:这是一个可选属性,该属性指定displayTag的CSS样式单属性。
pagelinksClass:这是一个可选属性,该属性指定DisplayTag的分页导航栏的CSS样式。
tableClass:这是一个可选属性,该属性指定DisplayTag的table元素的CSS样式。
columnClass:这是一个可选属性,该属性指定DisplayTag里table中每列的CSS样式。
baseUrl:这是一个可选属性,没有太大的作用。
postFunction:这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
为了使用AjaxTags的displayTag标签,必须先在Web应用中安装DisplayTags项目。安装DisplayTags可以按如下步骤进行:
将displaytag-{version}.jar文件复制到Web应用的WEB-INF/lib下。
将DisplayTag所依赖的JAR文件复制到Web应用的WEB-INF/lib下。这个步骤对于AjaxTags而言,往往已经完成了,因此无须额外复制。
如果使用JSP 1.1或者更早的容器,则应该将displaytag-{taglibversion}.tld文件复制到Web应用的WEB-INF/路径下,并在web.xml文件中配置标签库。配置标签库的代码如下:
<taglib>
<!-- 标签库所在的URI ->
<taglib-uri>http://displaytag.sf.net</taglib-uri>
<!-- 指定标签库TLD文件所在的物理路径 -->
taglib-location>/WEB-INF/displaytag-{taglibversion}.tld</taglib-location>
</taglib>
如果需要使用Display的导出选项(这种导出选项非常有用,它可以将表格显示的内容直接导出成XML文档和Excel文档等),则应该在web.xml文件中配置如下:
<filter>
<filter-name>ResponseOverrideFilter</filter-name>
<filter-class>org.displaytag.filter.ResponseOverrideFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ResponseOverrideFilter</filter-name>
<url-pattern>/displaytag.jsp</url-pattern>
</filter-mapping>
如果需要自定义DisplayTag显示的某些效果,则还需要增加一个displaytag.properties文件,关于该文件的各种属性以及具体含义可以参考DisplayTag的官方文档。下面是本示例应用增加在WEB-INF/classes路径下的displaytag.properties文件:
sort.behavior=list
sort.amount=list
basic.empty.showtable=true
basic.msg.empty_list=找不到满足要求的结果
paging.banner.placement=bottom
paging.banner.some_items_found=查询到{0}条记录, 当前显示从{2}到{3}条记录.
export.types=csv excel xml
export.amount=list
export.csv=false
export.excel=true
export.pdf=true
export.xml=false
export.excel.include_header=true
因为该文件中包含了中文字符,因此还必须使用native2ascii命令将该属性文件转为国际化的属性文件。经过这5个步骤,该Web应用就可以使用AjaxTags的displayTag标签了。在页面中使用displayTag标签的代码如下:
<jsp:useBean id="now" class="java.util.Date"/>
<!-- 直接初始化业务逻辑组件 -->
<jsp:useBean id="service" class="lee.BookService" />
<!-- 将display标签放在ajax:displayTag标签内,以便可以以Ajax方式进行排序、分页 -->
<ajax:displayTag id="displayTagFrame" ajaxFlag="displayAjax">
最后更新时间: ${now}
<display:table name="service.allBooks" class="displaytag" pagesize="10"
scope="page"defaultsort="1" defaultorder="descending" export="true"
id="row" excludedParams="ajax">
<!-- 输出业务逻辑组件中的两列 -->
<display:column property="name" title="书名" sortable="true" headerClass=
"sortable" />
<display:column property="publisher" title="出版社" sortable="true" headerClass=
"sortable" />
</display:table>
</ajax:displayTag>
在浏览器中浏览该页面,将看到如图18.9所示的效果。
图18.9 使用AjaxTags的displayTag标签
可以看到,页面中两次输出的时间并不相同,那是因为这里已经单击了“书名”列,从而按书名排序了,只是这种排序是以Ajax方式进行的,因此只刷新表格部分,并未刷新整个页面内容,从而看到两个时间并不相同。
如果单击表格下面的分页导航,则可看到以Ajax方式完成分页。如果单击下面的导出Excel按钮,将可以看到如图18.10所示的界面,这是DisplayTag的功能,与Ajax的displayTag并没有什么关系。
图18.10 导出Excel文档的效果
18.4.10 使用updateField标签
这个标签实现了一种常用的效果:当一个表单域的输入完成后,其他几个表单域的值根据该表单域的值计算得到。在大部分时候,如果这种计算无须服务器数据参与,则可以在客户端通过JavaScript计算完成。在某些情况下,例如,商品的折扣是通过后台程序设定的,则需要服务器数据的参与,因此应该使用该标签来完成。updateFiled标签有如下几个属性。
var:这是一个可选属性,该属性定义了autocomplete标签创建的JavaScript对象名。通常无须指定该属性。
attachTo:这是一个可选属性,该属性定义了var对应的自动完成对象将应用到的对象。
baseUrl:这是一个必需属性,该属性指定了Ajax请求发送的URL。
source:这是一个必需属性,该属性指定一个表单域,该表单域的值将作为请求参数随Ajax请求向服务器发送。
target:这是一个必需属性,该属性的值以逗号隔开,指定了一系列的表单域,Ajax响应的结果将在这些表单域中输出。
action:这是一个必需属性,该属性指定的HTML元素能触发onclick事件,该事件将触发Ajax交互。
parameters:这是一个可选属性,该属性指定需要发送到服务器端的请求参数。
eventType:这是一个可选属性,该属性指定能触发Ajax请求的事件类型。
preFunction:这是一个可选属性,该属性指定了Ajax交互之前自动执行的函数。
postFunction:这是一个可选属性,该属性指定了Ajax交互完成后自动执行的函数。
errorFunction:这是一个可选属性,该属性指定服务器响应出错时执行的函数。
parser:这是一个可选属性,该属性指定一个服务器响应的解析器,默认采用ResponseHtmlParser解析器;如果使用XML响应,则通常指定为ResponseXmlParser。
值得注意的是,该标签的响应一样是一个标准的AjaxTags响应,该响应包含的item节点数应与需要动态计算的表单域的数量相等,而且每个item节点的name节点值应与目标表单域的ID属性相同。
下面的应用示范了通过一个初始价格计算出五星级会员、四星级会员、三星级会员、二星级会员和一星级会员的会员价。计算打折价的Servlet的代码如下:
public class CalDiscountServlet extends BaseAjaxServlet
{
//重写getXmlContent方法,该方法返回的XML字符串作为Ajax请求的响应
public String getXmlContent(HttpServletRequest request, HttpServletResponse
response)
{
//price为初始价
double price = 0;
//下面5个变量分别为不同级别会员的打折价
double five = 0;
double four = 0;
double three = 0;
double two = 0;
double one = 0;
//获取请求参数
price = Double.parseDouble(request.getParameter("price"));
//调用服务器计算
five = price * 0.7;
four = price * 0.8;
three = price * 0.85;
two = price * 0.9;
one = price * 0.95;
//构造响应的XML字符串,并返回
return new AjaxXmlBuilder()
.addItem("five", Double.toString(five))
.addItem("four", Double.toString(four))
.addItem("three", Double.toString(three))
.addItem("two", Double.toString(two))
.addItem("one", Double.toString(one))
.toString();
}
}
上面代码中的addItem有两个参数:第一个参数分别为five和four等,这些参数并不是随意填写的,应与页面中需要通过服务器计算的表单域的ID属性相同,即页面中的five表单域的值等于Double.toString(five)。
页面中使用updateField标签来完成该Ajax交互,因为同时有5个表单域需要通过计算得到,因此target的值为以逗号隔开的5个值。下面是页面中使用updateField的代码片段:
<ajax:updateField
baseUrl="calDiscount"
//发送自动计算的源表单域
source="price"
//下面5个表单域将通过计算得到
target="five,four,three,two,one"
//action元素触发Ajax请求
action="action"
//发送的请求参数
parameters="price={price}"
parser="new ResponseXmlParser()"/>
在页面中的初始价格文本框中输入“80”,然后单击“计算”按钮,将出现如图18.11所示的界面。
图18.11 服务器计算表单域的值
18.5 关于AjaxTags的选择
正如前面介绍的,通过使用AjaxTags标签完成一个Ajax应用是如此简单,对于常见的Ajax应用场景,AjaxTags都提供了对应的封装,程序开发者只需要使用JSP标签即可开发出功能完备的Ajax应用。但AjaxTags并不是万能的,有些时候,我们必须放弃AjaxTags,选择更烦琐的开发方式。
18.5.1 AjaxTags的优势和使用场景
AjaxTags的优势相当明显,当Ajax技术面世时,有这样一种说法:Ajax通过对程序员的折磨来取悦用户。这种说法在某种程度上是对的,但所有的技术都以改善用户感受为最终目标,对于一个程序员而言,能带给用户更好的体验就是最大的成就。
Ajax技术的烦琐不言而喻,JavaScript本身不是一门严格的语言,缺乏严格的调试机制,即使在底层所有响应完成后,程序员还必须在表现层完成异常烦琐的DOM更新,还必须应用CSS样式。如果再加上跨浏览器支持、向后兼容性等一系列的技巧,那么开发一个普通的Ajax页面可能需要两天时间,这简直不可想象。
多亏了大量的JavaScript库,例如Prototype.js和Dojo等,但即使使用这些JavaScript库,我们依然需要面对很多问题,依然需要动态更新DOM,依然必须编写大量的JavaScript代码。
实际上,大量的Ajax应用场景重复出现,级联下拉框、自动完成、页面提示……每个常用的Ajax交互都需要花费大量的时间和精力。
AjaxTags对这些常用的Ajax交互场景提供了封装,程序开发者几乎无须编写任何JavaScript代码就可以完成一个专业的Ajax应用。特别是对于J2EE应用开发者而言,编写一个传统的Servlet,并将该Servlet配置在Web应用中,然后在页面中使用Ajax标签即可完成一个Ajax应用,相当简单。
AjaxTags最大的优点是简单,J2EE应用开发者甚至无须了解Ajax技术细节,只需要会编写Servlet,会使用JSP标签,就可以开发出专家级的Ajax应用,这是AjaxTags提供的最大好处。
因为AjaxTags简单,所以也可以大大节省开发者的时间。
对于所有能使用AjaxTags的情况,推荐优先考虑使用AjaxTags,因为使用AjaxTags既可以节省时间,也可以避免错误。AjaxTags的更新非常快,经常有新的标签加入,每个beta版之间的标签数量、标签的用法也存在差异,希望读者在使用AjaxTags时到其官方站点看一看AjaxTags的最新版包含了那些简便的标签。
18.5.2 AjaxTags的缺点
AjaxTags以简单、快捷的特性方便了J2EE的Ajax开发者,但它也不是万能的,在某些情形下,它依然存在一些小小的缺陷。AjaxTags大致存在如下缺陷:
AjaxTags只能在J2EE应用环境下使用,不能在其他Web编程脚本语言(如ASP和PHP等)中使用。
AjaxTags的高度封装虽然简化了Ajax的开发,但导致灵活性严重丧失;对于复杂的Ajax交互,使用AjaxTags完成更加烦琐。
AjaxTags对Ajax交互提供了封装,但不像Dojo那样提供了一个调试环境。整个Ajax交互不仅对普通浏览者透明,对于应用开发者也是透明的,调试难度相对较大。
虽然存在这些缺点,但AjaxTags的简单远远可以掩盖这些缺陷,在能使用AjaxTags标签的地方,应该尽量考虑使用AjaxTags。如果需要对AjaxTags进行大量扩展、修改,则应该考虑使用其他技术。毕竟,AjaxTags与其他Ajax技术并不是互斥的,例如Prototype.js本身就是AjaxTags所依赖的技术;如果有必要,还可以引入Dojo。