一、 在开始之前。
我想,在开始之前我要说的主要是两点,第一点是技术方面的。
在网站中使用AJAX方式来处理某些事务已经被广泛应用于各大网站,关于AJAX的解释我不想多说了。这里我主要先和大家说一下在本例中使用的AJAX解决方案。
在本例中,我们需要制作一个使用AJAX的二级联动菜单,当页面首次载入完毕时,客户端脚本程序将通过AJAX向服务器端的load.asp文件请求当前的商品的一级分类(在案例中称为父级分类)的信息,并填充在网页中的选择框控件(select控件)中。当用户通过键盘或者鼠标改变了显示父级分类的select控件,那么这时将通过AJAX再次向load.asp文件请求响应的父级分类的子分类的信息,并填充在显示子分类的select控件中。
在服务器端的load.asp文件中,我们将根据用户的请求,获得响应的父级分类或者指定父级分类的子分类的列表,并生成一个xml文件响应流返回。
我要说的第二点则是从网站整体设计来考虑的,我想,在开始制作一个AJAX的功能之前,你所需要反复思考的是,当前的功能是否真的需要用AJAX来实现。要知道,AJAX是需要客户端向服务器端请求的,而且这个请求一般还是动态网页的请求,这也就是说,服务器端因为这个AJAX的功能,必定会进行一些运算。这个运算是否是必要的呢,服务器端是否因此增加了无谓的额外运算呢。我打个比方来说,如果你用AJAX来处理月份和日期选择,例如选择1月,则日期的选择框变为31天。这就是不必要的,这可以简单的通过javascript来完成,而并不需要和服务器再通讯了。一般来说,只有非常必要的通过数据库查询的功能,例如,用户名是否重复等,这些才真的需要AJAX。另外,滥用AJAX也可能导致用户的可用性变差从而违背了开始使用AJAX的初衷。虽然AJAX技术很新潮,但是完全没有必要因此处处使用,还是要根据需求来确定功能的。
二、 制作服务器端响应程序
废话不多说,我们先进入服务器段相应程序的制作。在前面我简单说到了,本案例的功能是实现商品二级分类的选择,那么首先来看我为本案例设计的数据库,当然,这并不是本教程的重点,我将直接给出数据库的逻辑结构。
数据表Category:
字段名
|
类型
|
默认值
|
是否为空
|
备注
|
CategoryID
|
自动编号
|
|
否
|
类别编号
|
CategoryParent
|
数字
|
0
|
否
|
类别的父类别编号,如果本身就是父类别,则此编号为0
|
CategoryName
|
文本
|
|
否
|
类别名称
|
下面是我输入的一些测试用的数据:
Category
| ||
CategoryID
|
CategoryParent
|
CategoryName
|
1
|
0
|
书籍
|
2
|
0
|
礼品
|
3
|
0
|
日常用品
|
4
|
0
|
户外用品
|
5
|
1
|
小说
|
6
|
1
|
计算机书籍
|
7
|
1
|
经管书籍
|
8
|
2
|
鲜花
|
9
|
2
|
贺卡
|
10
|
2
|
模型
|
11
|
3
|
个人护肤品
|
12
|
3
|
男装
|
13
|
3
|
女装
|
14
|
3
|
厨房用品
|
15
|
4
|
军刀
|
16
|
4
|
帐篷
|
17
|
4
|
户外衣物
|
18
|
0
|
无下级分类测试
|
下面开始设计我们的服务器端响应页面load.asp
Load.asp页面要完成的工作有两个,第一是取出父级分类,第二是根据用户指定的父级分类编号来取出父级分类下的子分类。
这两个工作的不同主要体现在打开记录集时的sql语句的不同,因此我们根据用户请求时附加的QueryString参数type的不同来建立不同的SQL字符串。
建立完毕SQL字符串后,打开记录集,而后对记录集进行循环,在循环时要生成xml文件内容,这里为了方便,我们就不使用xml对象来生成xml文件,而是直接将xml文件的内容放在字符串内。当然,这样做有一个缺点就是在多次循环中使用多次的字符串拼接,我们会在后面谈到这个地方的优化。
先来看一下load.asp文件的完整代码。






















































首先是,我们在文件的头部使用了




这一段代码,这是为了避免客户端在使用xmlhttp对象来获取服务器端网页时,在第二次及以后的请求使用其本地的缓存而导致数据未能更新的情况,首先设定过期的绝对时间为当前时间的上一秒,而后设定过期时间间隔为0秒,而后设定cache-control的HTTP头为“no-cache”从而避免了客户端和代理服务器对页面的缓存。
而后我们又使用了代码





来避免当前的页面被直接查看。该段代码检查网页的来路,如果是直接查看当前页,则HTTP_REFERER的ServerVariables变量会为空,此时需要输出本页面不允许被直接查看的字样并且终止页面运行。这主要是为了在一定程度上隐藏系统数据传输架构,从而增强安全性,当然,仅是在一定程度上而已。
三、 制作客户端的AJAX脚本
而后我们将进入客户端的脚本制作阶段。在本案例中使用了prototype.js的js代码库来完成AJAX的功能。Prototype代码库中有Ajax对象来负责ajax数据的传输,你只需要指定目标的url、传递的参数以及回调函数即可完成AJAX的使用,从而不必关心如何建立可移植的xmlhttp对象,请求构建这些具体的事情。
首先需要在<head>区域引入prototype.js代码库,如果你没有这个文件,可以则其官方网站
http://www.prototypejs.org下载最新版本。
引入完毕prototype.js文件后,我们先开始对页面的请求进行一个策划,我们一共应该有两组ajax请求。第一组是当页面载入完毕时,获取父级分类列表,第二组则是当用户选定某个父级分类时,获取其子分类。
首先我们来看页面的html的body部分:

















你可以看到,在这里我们在一个表单中建立了两个select控件,其中父级分类的select控件名称为category_parent,子级分类的select控件为category_child,这两个select控件的初始状态均为不可用(disabled),这是因为在页面载入完成后,实际上这两个控件中都不包含可用的数据。从用户可用性的角度来讲,我们需要设定其为不可用的状态,并加入文字的声明,从而使客户迅速了解到当前他所面临的情况。
下面来看写在head区域的使用ajax读取父级分类的js代码。
























































首先,获取显示父分类的select控件为变量sel,而后我们使用while循环来删除sel中每一个option选项控件,而后根据prototype的ajax.request回调此函数传入的参数originalRequest,此参数为一个xmlhttp对象,我们首先获得次对象的文档元素,作为变量oXMLDocument,而后判断该文档元素的根标签是否为error,如果为error,则说明在服务器段的处理出现了异常,此时我们需要显示这个异常,这里调用的addSelectElement函数用于在指定的select控件中增加一个option。
如果文档根元素的标签不是error,则说明此时服务器端处理是正确的,则我们需要便利xml文档中的子节点,并根据子节点的值和子节点的id属性分别设定要加入的option控件的显示文本和值。
下面再来看读取子分类部分的AJAX请求的构建:






























你会发现,这里的构建请求与读取父分类部分基本相似。在这里我们构建请求之前还进行了一些操作,即删除了子分类的select控件的所有option控件,同时设定该控件为disabled状态,再为控件填入一个显示“载入中…”字样的option,这也是处于用户体验的角度来考虑的。这里我插一句,我们不是天天把用户体验挂在嘴边的,而是应当在做项目的时候时时的考虑,原有的模式是否是真正的好的用户体验,并且尝试改进原有的方式来增加用户体验。
本函数的其他部分和回调函数completeAJAX_Child都基本与请求父分类列表的相应部分相似,这里我就不再赘述了。有问题的朋友可以下载我的源代码进行调试,里面的注释非常详细。
四、 还要说的一些话
在这里我对上面没有详细说的一些问题进行一个简单的补充。
首先是本实例的效率问题。其实从我个人角度来讲,这样产品分类的二级联动菜单是不适合使用AJAX的。(当然,本教程使用产品分类作为二级联动菜单的实例是想用一个你熟悉和常见的例子,使你便于理解,以免在对项目本身的疑问的基础上来学习AJAX。)
为什么不适合呢,因为产品的二级分类这种东西其实是很少变动的,我们完全可以将其生成一个可以由js处理的静态的二级分类菜单。
使用静态的二级分类菜单可以大大避免一次无用的http请求,从而节约服务器和带宽资源。那么,必须和服务器通讯的信息(如信息本身生成js文件太庞大,从而使用AJAX来读取单次单个的信息而并非将所有分类和子分类一起放在js文件中下载),而且在服务器端读取数据库的开销也非常大时,我们可以考虑在服务器端生成静态的xml文件,例如,本例中可以对每一个父级分类生成一个xml文件,并根据父级分类的编号明明为1.xml、2.xml等,在请求时直接根据父级分类的编号来调用相应的文件即可。
而具体在本例中的在多次循环中的多次字符串链接,我们则可以使用xml对象来解决这一问题,另外也可以自己仿照.Net中的StringBuilder对象来写一个StringBuilder对象来完成字符串拼接的工作。
最后,我想给大家留一个作业,就是,大家不妨将这个小案例改装成为三级分类使用,并且根据当前的一级、二级分类是否包含下一级分类,来控制下一级分类的select控件的显示与否。