Web应用通常使用“向导工具”设计原则来构建,即每个屏幕要求用户输入少量的信息,每个后续页的数据都依据前一页的输入来创建。对于某些情况,这个设计模式非常有用,如用户以一种逐步、有序的方式完成任务。遗憾的是,太多的Web应用使用了这种方法,因为它们别无选择。在Ajax技术出现之前,当基于用户输入修改页面上的某些部分时,动态地更新页面而不刷新整个页面是很难办到的,甚至根本不可能。
避免完全页面刷新的一种技术是在页面上隐藏数据,并在需要时再显示它们。例如,假设选择框B的值要根据选择框A中所选值来填写,此时选择框B的所有可取值就可以放在隐藏的选择框中。当选择框A中的所选值有变化时,JavaScript可以确定要显示哪一个隐藏的选择框,然后将该选择框置为可见,再把前一个选择框置为隐藏。这种技术还可以变化一下,用隐藏列表框中的元素动态填写选择框B中的option元素。这些技术都很有用,但是它们只在有限的情况下可用,即页面中仅限于根据用户输入对有限的选择进行修改,而且这样的选择必须相对少。
假设你在构建一个在线的汽车分类广告服务。某人想购买汽车,指定了车型年份、品牌和车型,来搜索他想买的汽车。为了避免用户的输入错误,并减少所需的动态验证次数,你决定车型年份、品牌和车型输入字段都应当是选择框,而且要考虑过去25年的车型广告。如果车型年份选择框或品牌选择框中的选择发生变化,就必须修改对应该车型年份和品牌的可用车型列表。
要记住,对于每个车型年份,都会出现一些新的品牌,而一些老牌子可能会淡出人们的视线,所以其个数也会有变化。还要记住对于每种品牌来说,每年的车型都可能不同。如果有数十种品牌,每个车型年份每种品牌都有多种车型,那么车型年份、品牌和车型的组合数将是惊人的。由于有这么多的组合,只使用JavaScript来填写选择框是不可能的。
使用Ajax技术就能很轻松地解决这个问题。车型年份或品牌选择框中的选择每次有变化时,会向服务器发出异步请求,要求得到该车型年份特定品牌的车型列表。服务器负责根据浏览器所请求的品牌和车型年份来确定车型列表。服务器很可能采用一种高速的数据查找组件(可能实现为一个关系数据库),以完成查找可用车型的具体工作。一旦找到可用的车型,服务器把它们打包在一个XML文件中,并返回给浏览器。
浏览器负责解析服务器的XML响应,并用指定品牌和车型年份的可用车型来填写车型选择框。在这个例子中,要注意数据视图与原始数据得到了很好的分离。浏览器只负责呈现数据视图,服务器则负责挖掘必须呈现在浏览器视图上的原始数据。
代码清单4-5展示了如何使用Ajax技术,从而根据另外两个列表框的值动态创建一个选择框的内容。这个例子的用例就是以上所述的分类广告服务,在此车型年份选择框和品牌选择框中的所选值决定了车型选择框中的内容。这个例子中只用了4个车型年份、3种品牌,以及对于某个车型年份、特定的品牌的4种可用车型。即便如此,车型年份、品牌和车型的组合数也达到了48。如果采用隐藏的办法,即对应每个车型年份和品牌组件,将相应的车型列表隐藏起来,并根据所选的品牌和车型年份值来显示适当的列表,这是不可行的。
代码清单4-5 dynamicLists.htm
Code
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Dynamically Filling Lists</title>
<script type="text/javascript">
var xmlHttp;
function createXmlHttpRequest(){
if(window.ActiveXObject){
xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
}
else if(window.XMLHttpRequest){
xmlHttp=new XMLHttpRequest();
}
}
function refreshModelList(){
var make=document.getElementById("make").value;
var modelYear=document.getElementById("modelYear").value;
if(make==""||modelYear==""){
clearModelsList();
return;
}
var url="RefreshModelList.aspx?"+createQueryString(make,modelYear)+"&ts="+new Date().getTime();
createXmlHttpRequest();
xmlHttp.onreadystatechange=handleStateChange;
xmlHttp.open("Get",url,true);
xmlHttp.send(null);
}
function createQueryString(make,modelYear){
var queryString="make="+make+"&modelYear="+modelYear;
return queryString;
}
function handleStateChange(){
if(xmlHttp.readyState==4){
if(xmlHttp.status==200){
updateModelsList();
}
}
}
function updateModelsList(){
clearModelsList();
var models=document.getElementById("models");
var results=xmlHttp.responseXML.getElementsByTagName("model");
var option=null;
for(var i=0;i<results.length;i++){
option=document.createElement("option");
option.appendChild(document.createTextNode(results[i].firstChild.nodeValue))
models.appendChild(option);
}
}
function clearModelsList() {
var models=document.getElementById("models");
while(models.childNodes.length>0){
models.removeChild(models.childNodes[0]);
}
}
</script>
</head>
<body>
<h1>Selelt Model Year and Make</h1>
<br /><br />
<span style="font-weight:bold;">Make Year:</span>
<select id="modelYear" onchange="refreshModelList();">
<option value="">Selelt One</option>
<option value="2006">2006</option>
<option value="1995">1995</option>
<option value="1985">1985</option>
<option value="1970">1970</option>
</select>
<br /><br />
<span style="font-weight:bold;">Make:</span>
<select id="make" onchange="refreshModelList();">
<option value="">Selelt One</option>
<option value="Chevrolet">Chevrolet</option>
<option value="Dodge">Dodge</option>
<option value="Pontiac">Pontiac</option>
</select>
<br /><br />
<span style="font-weight:bold;">Models:</span>
<select id="models" size="6" style="width:300px;">
</select>
</body>
</html>
页面的更新由品牌和车型年份选择框的onchange事件驱动。只要这两个选择框中任何一个的所选值有变化,浏览器就会向服务器发出异步请求。发送请求时会携带一个查询串,其中包含所选品牌和车型年份的值。
RefreshModelList.aspx从浏览器接收到请求,并确定对应指定品牌和车型年份的车型列表。这个servlet首先解析查询串,确定所请求的品牌和车型年份。一旦确定了品牌和车型年份,服務端会迭代处理一个对象集合,其中每个对象分别表示一种车型年份、品牌和车型的组合。如果特定对象的车型年份和品牌属性与所请求的车型年份和品牌匹配,则把这个对象的车型属性增加到响应XML串中。找到对应指定品牌和车型年份的所有车型之后,将响应XML写回到浏览器。
请注意,在实际实现中,服务器端组件不太可能依赖硬编码的值填写选择框,而是会搜索一个高速数据库,查找所请求车型年份和品牌的相应车型。
代码清单4-6 RefreshModelList.aspx
Code
<%@ Page Language="C#" %>
<script runat="server">
private static ArrayList availableModels = new ArrayList();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
Response.ContentType = "text/html;charset=UTF-8";
int modelYear = Convert.ToInt32(Request["modelYear"]);
string make = Request["make"];
StringBuilder results = new StringBuilder("<models>");
availableModels.Clear();
this.addMakeModelYear();
foreach (object obj in availableModels)
{
MakeModelYear data = obj as MakeModelYear;
if (data != null)
{
if (data.ModelYear == modelYear)
{
if (data.Make.Equals(make))
{
results.Append("<model>");
results.Append(data.Model);
results.Append("</model>");
}
}
}
}
results.Append("</models>");
Response.ContentType = "text/xml";
Response.Write(results.ToString());
}
}
public virtual void addMakeModelYear()
{
availableModels.Add(new MakeModelYear(2006, "Dodge", "Charger"));
availableModels.Add(new MakeModelYear(2006, "Dodge", "Magnum"));
availableModels.Add(new MakeModelYear(2006, "Dodge", "Ram"));
availableModels.Add(new MakeModelYear(2006, "Dodge", "Viper"));
availableModels.Add(new MakeModelYear(1995, "Dodge", "Avenger"));
availableModels.Add(new MakeModelYear(1995, "Dodge", "Intrepid"));
availableModels.Add(new MakeModelYear(1995, "Dodge", "Neon"));
availableModels.Add(new MakeModelYear(1995, "Dodge", "Spirit"));
availableModels.Add(new MakeModelYear(1985, "Dodge", "Aries"));
availableModels.Add(new MakeModelYear(1985, "Dodge", "Daytona"));
availableModels.Add(new MakeModelYear(1985, "Dodge", "Diplomat"));
availableModels.Add(new MakeModelYear(1985, "Dodge", "Omni"));
availableModels.Add(new MakeModelYear(1970, "Dodge", "Challenger"));
availableModels.Add(new MakeModelYear(1970, "Dodge", "Charger"));
availableModels.Add(new MakeModelYear(1970, "Dodge", "Coronet"));
availableModels.Add(new MakeModelYear(1970, "Dodge", "Dart"));
availableModels.Add(new MakeModelYear(2006, "Chevrolet", "Colorado"));
availableModels.Add(new MakeModelYear(2006, "Chevrolet", "Corvette"));
availableModels.Add(new MakeModelYear(2006, "Chevrolet", "Equinox"));
availableModels.Add(new MakeModelYear(2006, "Chevrolet", "Monte Carlo"));
availableModels.Add(new MakeModelYear(1995, "Chevrolet", "Beretta"));
availableModels.Add(new MakeModelYear(1995, "Chevrolet", "Camaro"));
availableModels.Add(new MakeModelYear(1995, "Chevrolet", "Cavalier"));
availableModels.Add(new MakeModelYear(1995, "Chevrolet", "Lumina"));
availableModels.Add(new MakeModelYear(1985, "Chevrolet", "Cavalier"));
availableModels.Add(new MakeModelYear(1985, "Chevrolet", "Chevette"));
availableModels.Add(new MakeModelYear(1985, "Chevrolet", "Celebrity"));
availableModels.Add(new MakeModelYear(1985, "Chevrolet", "Citation II"));
availableModels.Add(new MakeModelYear(1970, "Chevrolet", "Bel Air"));
availableModels.Add(new MakeModelYear(1970, "Chevrolet", "Caprice"));
availableModels.Add(new MakeModelYear(1970, "Chevrolet", "Chevelle"));
availableModels.Add(new MakeModelYear(1970, "Chevrolet", "Monte Carlo"));
availableModels.Add(new MakeModelYear(2006, "Pontiac", "G6"));
availableModels.Add(new MakeModelYear(2006, "Pontiac", "Grand Prix"));
availableModels.Add(new MakeModelYear(2006, "Pontiac", "Solstice"));
availableModels.Add(new MakeModelYear(2006, "Pontiac", "Vibe"));
availableModels.Add(new MakeModelYear(1995, "Pontiac", "Bonneville"));
availableModels.Add(new MakeModelYear(1995, "Pontiac", "Grand Am"));
availableModels.Add(new MakeModelYear(1995, "Pontiac", "Grand Prix"));
availableModels.Add(new MakeModelYear(1995, "Pontiac", "Firebird"));
availableModels.Add(new MakeModelYear(1985, "Pontiac", "6000"));
availableModels.Add(new MakeModelYear(1985, "Pontiac", "Fiero"));
availableModels.Add(new MakeModelYear(1985, "Pontiac", "Grand Prix"));
availableModels.Add(new MakeModelYear(1985, "Pontiac", "Parisienne"));
availableModels.Add(new MakeModelYear(1970, "Pontiac", "Catalina"));
availableModels.Add(new MakeModelYear(1970, "Pontiac", "GTO"));
availableModels.Add(new MakeModelYear(1970, "Pontiac", "LeMans"));
availableModels.Add(new MakeModelYear(1970, "Pontiac", "Tempest"));
}
public class MakeModelYear{
private int modelYear;
private string make;
private string model;
public MakeModelYear()
{
}
public MakeModelYear(int modelYear,string make,string model)
{
this.modelYear = modelYear;
this.make = make;
this.model = model;
}
public int ModelYear
{
get
{
return modelYear;
}
}
public string Make
{
get
{
return make;
}
}
public string Model
{
get
{
return model;
}
}
}
</script>