传统方式校验用户名是否重复的设计分析
RegUserUI
Reguser.jsp
RegUser
Main.jsp
注册
校验用户名
是否重复
VerifyUserName
?返回怎样的
一个页面
VerifyUserName这个Action校验用户名后还要回到第一个jsp页面上,而第一个jsp页面是第一个action显示出来的,所以也可以在第一个action的基础上增加校验用户名的功能,这就没有做到职责单一了,思路没有单独做一个VerifyUserName的action来校验用户名清晰。
在校验用户名的action和jsp页面中要增加怎样的代码呢?由于显示结果是增加一条提示信息而已(用<div>将提示信息包起来, 有信息时显示信息,没信息时保持空白),所以在action中也就是要增加一条存储提示信息的代码,写完action后,问题就是jsp页面上如何实现将校验用户名是否存在的请求及参数提交给第一个action了,这时候必须写javascript函数了,并应掌握<html:rewrite>标签和document.location.href属性。
传统实现方式1 :在原来的主窗口中回显结果
问题:
1:回显的页面在原来网页效果的基础上增加一点内容,可是服务器的响应结果会覆盖掉窗口中原来显示的那个网页内容,如果服务器在响应时仅仅回送要新增的内容,则会覆盖掉原来的整个网页,怎样做才能看到原来网页效果的基础上增加一点内容?
2:怎样用javascript代码将请求发送给服务器,在发送请求时,怎样将文本框中填写的用户名作为参数传递给服务器。
3:怎样在<a>标签中触发javascript函数调用?触发函数时要注意返回值。
实现思路:
让VerifyUserName仍然返回Reguser.jsp,这时候需要在Reguser.jsp页面中增加一条有时候显示,有时候不显示的提示信息。或者是通过生成一段javascript代码,然后用一个弹出对话框来显示提示信息。先用浏览器直接测试 VerifyUserName。
在<a>标签中可以使用javascript协议或onclick事件来触发javascript函数调用,先用静态数据测试一下,然后用window.location.href属性(replace方法)或模拟表单提交的方式向服务器发送请求,把两种方式都要做一下。
缺点:
要为电脑增加一个鼠标,由于浏览器的特点是买回鼠标就会覆盖掉原来的电脑,所以,要想显示出电脑加鼠标的效果,只能同时去买回一个电脑和鼠标。
好比舞台要切换帷幕一样,上一场的帷幕上贴的是花,当下一场要换成贴龙时,最快的方式不是把原来帷幕上的花揭下来再换上龙,而是做两个帷幕,直接把上一个帷幕收起,同时把下一个帷幕拉开。每次都送出一个帷幕,帷幕上贴不同内容。
<html:base> 标签会害你的,例如,它生成的路径<base href=“http://localhost:8080/ajaxdemo/WEB-INF/user/RegUser.jsp”>,我们网页中的相对路径全变成/Web-inf/user下面的了。虽然我在课堂上提前讲了,但在练习时,很多同学还是都犯了这个错误并找不出原因来。
讲超链接的javascript协议时,先用这个代码说一下:<a href=“javascript:3”></a>,让大家明白返回值的而影响,也可以直接在浏览器地址栏输入javascript:window.document,浏览器将显示[object]。
对于onclick的讲解:
1.在保留<html:base/>标签时,用用<a href=‘<html:rewrite action=“#’ onclick=”verifyUserName()“>做,看到<html:base/>标签导致的错误后果。照理说,超链接这里用# 不应该发送请求的,但我把jsp页面放在web-inf里时,浏览器要发请求,有同学没放在web-inf里,结果没发请求。
2.去掉<html:base/> 标签,看到正确的效果后,
3.增加超链接,将#改为空白,即改成如下形式:
<a href=“” onclick=“verifyUserName()”>校验用户名是否存在</a>
在这里说明清楚了如果不返回false,除了干事件处理代码,还干原来默认的行为,关于如果取消原来的行为,看完下面的实验后继续讲解。
4.增加超链接,让路径指向VerifyUserName,
<a href='<html:rewrite action="/VerifyUserName"/>' onclick="verifyUserName()">校验用户名是否存在</a>
这时候,即使用户名为zxx,也说没有重复,这是因为浏览器除了访问javascript中设置的VerifyUserName这个地址外,还执行了超链接原来的行为,又根据href的值再次发出了请求,一共发了两次请求,浏览器显示了后面的请求的结果。
5.通过3步和第4步的问题,引出了事件处理函数中的return false的作用,可以通过这个代码辅助说明:<a href="delAction?id=3" onclick="return confirm('真的吗?')">删除</a>
var url = '<html:rewrite action="/AjaxVerifyUserName?username="/>' + userName;
上面的为什么要用单引号引起来才成为javascript的字符串,否则会当作变量处理,课堂上专门演示和说明了,练习时还有同学犯这个错误,我觉得关键是要告诉大家是检查javascript代码时,不能在jsp页面检查,而应该在其生成的网页内容中检查。做bs的界面时,最终的效果是浏览器显示网页而造成的,如果界面有问题,首先要查网页的源代码找出问题,然后才能推断出jsp在生成网页源代码时出现了什么问题,这对于很多人来说都不知道。
对于第2个问题:javascript是一种对它所在的网页文档及网页上的各个元素进行操作的语言,即可以对网页上的文本框进行增删改查,现在要获取用户名文本框中的内容,你说怎么办?
预备的实验内容:
<form>
用户名:<input type="text" name="username" /><br>
密码:<input type="password" name="password" /><br>
确认密码:<input type="password" name="password2" /><br>
<input type="submit" value="注册"/>
</form>
校验用户名是否存在
恭喜你,用户名未被注册!
很遗憾,用户名已被注册!
传统实现方式2 :用弹出的新窗口回显结果
实现方式:在弹出窗口中回显结果
弹出窗口演示
模态对话框演示
实现思路:
由弹出窗口打开一个网页的方式发出校验用户名的请求,回送的应该是一个网页,只是这个网页的内容很简单,但是,如果要有关闭按钮,必须加上相应的按钮和javascript代码。
模态对话框的好处在于避免了受浏览器显示新窗口的方式的差异的影响,并可要求用户必须关闭弹出窗口后才能进行其他操作。
特点:
服务器回送的结果给新窗口,不影响原始窗口。一个帷幕收起,同时把下一个帷幕拉开。每次都送出一个帷幕,帷幕上贴不同内容。
<html:base> 标签会害你的,例如,它生成的路径<base href=“http://localhost:8080/ajaxdemo/WEB-INF/user/RegUser.jsp”>,我们网页中的相对路径全变成/Web-inf/user下面的了。虽然我在课堂上提前讲了,但在练习时,很多同学还是都犯了这个错误并找不出原因来。
讲超链接的javascript协议时,先用这个代码说一下:<a href=“javascript:3”></a>,让大家明白返回值的而影响,也可以直接在浏览器地址栏输入javascript:window.document,浏览器将显示[object]。
对于onclick的讲解:
1.在保留<html:base/>标签时,用用<a href=‘<html:rewrite action=“#’ onclick=”verifyUserName()“>做,看到<html:base/>标签导致的错误后果。照理说,超链接这里用# 不应该发送请求的,但我把jsp页面放在web-inf里时,浏览器要发请求,有同学没放在web-inf里,结果没发请求。
2.去掉<html:base/> 标签,看到正确的效果后,
3.增加超链接,将#改为空白,即改成如下形式:
<a href=“” onclick=“verifyUserName()”>校验用户名是否存在</a>
在这里说明清楚了如果不返回false,除了干事件处理代码,还干原来默认的行为,关于如果取消原来的行为,看完下面的实验后继续讲解。
4.增加超链接,让路径指向VerifyUserName,
<a href='<html:rewrite action="/VerifyUserName"/>' onclick="verifyUserName()">校验用户名是否存在</a>
这时候,即使用户名为zxx,也说没有重复,这是因为浏览器除了访问javascript中设置的VerifyUserName这个地址外,还执行了超链接原来的行为,又根据href的值再次发出了请求,一共发了两次请求,浏览器显示了后面的请求的结果。
5.通过3步和第4步的问题,引出了事件处理函数中的return false的作用,可以通过这个代码辅助说明:<a href="delAction?id=3" onclick="return confirm('真的吗?')">删除</a>
var url = '<html:rewrite action="/AjaxVerifyUserName?username="/>' + userName;
上面的为什么要用单引号引起来才成为javascript的字符串,否则会当作变量处理,课堂上专门演示和说明了,练习时还有同学犯这个错误,我觉得关键是要告诉大家是检查javascript代码时,不能在jsp页面检查,而应该在其生成的网页内容中检查。做bs的界面时,最终的效果是浏览器显示网页而造成的,如果界面有问题,首先要查网页的源代码找出问题,然后才能推断出jsp在生成网页源代码时出现了什么问题,这对于很多人来说都不知道。
对于第2个问题:javascript是一种对它所在的网页文档及网页上的各个元素进行操作的语言,即可以对网页上的文本框进行增删改查,现在要获取用户名文本框中的内容,你说怎么办?
预备的实验内容:
<form>
用户名:<input type="text" name="username" /><br>
密码:<input type="password" name="password" /><br>
确认密码:<input type="password" name="password2" /><br>
<input type="submit" value="注册"/>
</form>
校验用户名是否存在
恭喜你,用户名未被注册!
很遗憾,用户名已被注册!
Ajax的概念:
是asynchronous javascript and xml的简写。
不是一项具体的技术,而是几门技术的综合应用。
其核心只不过是要在javascript中调用一个叫XMLHttpRequest的javascript类,这个类可以与Web服务器使用HTTP协议进行交互,程序不通过浏览器发出请求,而是用这个特殊的JavaScript对象发送请求,再由这个JavaScript对象接收响应,并将响应结果用DOM编程方式挂到原来的网页上(见下页的图),从而使得javascript借助这个api类可以干出比较有意义的事情。
XMLHttpRequest对象在网络上的俗称为XHR对象。
Ajax的特点:
浏览器中显示一个页面后,这个页面以后一直不改变,所有的操作请求都由这个网页中的javascript代码发出,所有的结果都由javascript代码接受并增加到这个页面上,浏览器窗口中显示的网页始终都是初始的那个网页。(见下面两页的图)
增强用户体验:可以在用户浏览网页的同时与服务器进行异步交互和实现网页内容的局部更新,例如,126邮箱密码安全性判断和google suggest;可以按需取数据,改善页面显示速度,例如,树状菜单和babasport的首页(整合多个信息的页面);视觉流畅的定时刷新,例如,聊天室。(用下几页的图举例说明)
学习ajax和应用ajax的难点不在于XMLHttpRequest本身,而在于javascript和DOM编程,没有较好的javascript和DOM编程基础,你就很难做出有意义的ajax应用。
浏览器的普通交互方式
Ajax的交互方式
同步交互和异步交互
举个例子:普通B/S模式(同步) AJAX技术(异步)
* 同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
* 异步: 请求通过事件触发->服务器处理(这时浏览器仍然可以作其他事情)->处理完毕
同步是指:发送方发出数据后,等接收方发回响应以后才发下一个
数据包的通讯方式。 异步是指:发送方发出数据后,不等接收方发回响应,接着发送下
个数据包的通讯方式
易懂的理解:
异步传输: 你传输吧,我去做我的事了,传输完了告诉我一声 同步传输: 你现在传输,我要亲眼看你传输完成,才去做别的事
AJAX案例之google suggest
AJAX案例之Google Maps
Ajax的应用场景:财富通网吧充值界面
Ajax的应用场景:密码安全性检测
Ajax的应用场景:RIA应用
Ajax的应用场景:邮箱系统
Ajax的应用场景:蓝源批发零售业连锁管理系统
究竟什么是Ajax
Ajax:一种不用刷新整个页面便可与服务器通讯的办法
图1 Web的传统模型。客户端向服务器发送一个请求,服务器返回整个页面,如此反复
图2 在Ajax模型中,数据在客户端与服务器之间独立传输。服务器不再返回整个页面
Ajax的实现方式
不用刷新整个页面便可与服务器通讯的办法:
Flash
Java applet
框架:如果使用一组框架构造了一个网页,可以只更新其中一个框架,而不必惊动整个页面
隐藏的iframe
XMLHttpRequest:该对象是对 JavaScript 的一个扩展,可使网页与服务器进行通信。是创建 Ajax 应用的最佳选择。实际上通常把 Ajax 当成 XMLHttpRequest 对象的代名词
Ajax的工作原理
Ajax的核心是JavaScript对象XmlHttpRequest。
该对象在Internet Explorer 5中首次引入,它是一种支持异步请求的技术。简而言之,XmlHttpRequest使您可以使用JavaScript向服务器提出请求并处理响应,而不阻塞用户。
AJAX采用异步交互过程。AJAX在用户与服务器之间引入一个中间媒介,从而消除了网络交互过程中的处理—等待—处理—等待缺点。
用户的浏览器在执行任务时即装载了AJAX引擎。AJAX引擎用JavaScript语言编写,通常藏在一个隐藏的框架中。它负责编译用户界面及与服务器之间的交互。
AJAX引擎允许用户与应用软件之间的交互过程异步进行,独立于用户与网络服务器间的交流。现在,可以用Javascript调用AJAX引擎来代替产生一个HTTP的用户动作,内存中的数据编辑、页面导航、数据校验这些不需要重新载入整个页面的需求可以交给AJAX来执行。
使用AJAX,可以为JSP、开发人员、终端用户带来可见的便捷:
用户界面
AJAX引擎
服务器
用户界面
服务器
AJAX包含的技术
AJAX:(Asynchronous JavaScript and XML)并不是一项新技术,其实是多种技术的综合,包括Javascript、XHTML和CSS、DOM、XML和XMLHttpRequest.
服务器端语言:服务器需要具备向浏览器发送特定信息的能力。Ajax与服务器端语言无关。
XML (eXtensible Markup Language,可扩展标记语言) 是一种描述数据的格式。AJAX 程序需要某种格式化的格式来在服务器和客户端之间传递信息,XML 是其中的一种选择
XHTML(eXtended Hypertext Markup Language,使用扩展超媒体标记语言)和 CSS(Cascading Style Sheet,级联样式单)标准化呈现;
DOM(Document Object Model,文档对象模型)实现动态显示和交互;
使用XMLHTTP组件XMLHttpRequest对象进行异步数据读取
使用JavaScript绑定和处理所有数据
AJAX的缺陷
AJAX不是完美的技术。也存在缺陷:
1 AJAX大量使用了Javascript和AJAX引擎,而这个取决于浏览器的支持。IE5.0及以上、Mozilla1.0、NetScape7及以上版本才支持,Mozilla虽然也支持AJAX,但是提供XMLHttpRequest的方式不一样。所以,使用AJAX的程序必须测试针对各个浏览器的兼容性。
2 AJAX更新页面内容的时候并没有刷新整个页面,因此,网页的后退功能是失效的;有的用户还经常搞不清楚现在的数据是旧的还是已经更新过的。这个就需要在明显位置提醒用户“数据已更新”。
3 对流媒体的支持没有FLASH、Java Applet好。
4 一些手持设备(如手机、PDA等)现在还不能很好的支持Ajax。
XMLHttpRequest对象 XMLHttpRequest是XMLHTTP组件的对象,通过这个对象,AJAX可以像桌面应用程序一样只同服务器进行数据层面的交换,而不用每次都刷新界面,也不用每次将数据处理的工作都交给服务器来做;这样既减轻了服务器负担又加快了响应速度、缩短了用户等待的时间。
XMLHttpRequest最早是在IE5中以ActiveX组件的形式实现的。非W3C标准.
创建XMLHttpRequest对象(由于非标准所以实现方法不统一)
Internet Explorer把XMLHttpRequest实现为一个ActiveX对象
其他浏览器(Firefox、Safari、Opera…)把它实现为一个本地的JavaScript对象。
XMLHttpRequest在不同浏览器上的实现是兼容的,所以可以用同样的方式访问XMLHttpRequest实例的属性和方法,而不论这个实例创建的方法是什么。
XMLHttpRequest对象初始化
function createXmlHttpRequest(){
var xmlhttp = null;
try{
//Firefox, Opera 8.0+, Safari
xmlhttp=new XMLHttpRequest();
}catch(e){//IEIE7.0以下的浏览器以ActiveX组件的方式来创建XMLHttpRequest对象
var MSXML =
['MSXML2.XMLHTTP.6.0','MSXML2.XMLHTTP.5.0',
'MSXML2.XMLHTTP.4.0','MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP','Microsoft.XMLHTTP'];
for(var n = 0; n < MSXML.length; n ++){
try{
xmlhttp = new ActiveXObject(MSXML[n]);
break;
}catch(e){}}
}
return xmlhttp;
}
XMLHttpRequest对象方法
方法 |
描述 |
abort() |
停止当前请求 |
getAllResponseHeaders() |
把http请求的所有响应首部作为键/值对返回 |
getResponseHeader("headerLabel") |
返回指定首部的串值 |
open(“method”,”url”) |
建立对服务器的调用,method参数可以是GET,POST。url参数可以是相对URL或绝对URL。这个方法还包括3个可选参数。 |
send(content) |
向服务器发送请求 |
setRequestHeader("label", "value") |
把指定首部设置为所提供的值。在设置任何首部之前必须先调用open() |
XMLHttpRequest对象属性
发送请求--方法和属性介绍
利用XMLHttpRequest 实例与服务器进行通信包含以下3个关键部分:
onreadystatechange 事件处理函数
open 方法
send 方法
onreadystatechange:
该事件处理函数由服务器触发,而不是用户
在 Ajax 执行过程中,服务器会通知客户端当前的通信状态。这依靠更新 XMLHttpRequest 对象的 readyState 来实现。改变 readyState 属性是服务器对客户端连接操作的一种方式。
每次 readyState 属性的改变都会触发 readystatechange事件
open(method, url, asynch)
XMLHttpRequest 对象的 open 方法允许程序员用一个Ajax调用向服务器发送请求。
method:请求类型,类似 “GET”或”POST”的字符串。若只想从服务器检索一个文件,而不需要发送任何数据,使用GET(可以在GET请求里通过附加在URL上的查询字符串来发送数据,不过数据大小限制为2000个字符)。若需要向服务器发送数据,用POST。
在某些情况下,有些浏览器会把多个XMLHttpRequest请求的结果缓存在同一个URL。如果对每个请求的响应不同,这就会带来不好的结果。把当前时间戳追加到URL的最后,就能确保URL的惟一性,从而避免浏览器缓存结果。
url:路径字符串,指向你所请求的服务器上的那个文件。可以是绝对路径或相对路径。
asynch:表示请求是否要异步传输,默认值为true(异步)。指定true,在读取后面的脚本之前,不需要等待服务器的相应。指定false,当脚本处理过程经过这点时,会停下来,一直等到Ajax请求执行完毕再继续执行。
var url = "GetAndPostExample?timeStamp=" + new Date().getTime();
send(data):
open 方法定义了 Ajax 请求的一些细节。send 方法可为已经待命的请求发送指令
data:将要传递给服务器的字符串。
若选用的是 GET 请求,则不会发送任何数据, 给 send 方法传递 null 即可:request.send(null);
当向send()方法提供参数时,要确保open()中指定的方法是POST,如果没有数据作为请求体的一部分发送,则使用null.
完整的 Ajax 的 GET 请求示例:
使用get请求时send方法参数时null,如果传值的话,服务器也接受不到
setRequestHeader(header,value)
当浏览器向服务器请求页面时,它会伴随这个请求发送一组首部信息。这些首部信息是一系列描述请求的元数据(metadata)。首部信息用来声明一个请求是 GET 还是 POST。
Ajax 请求中,发送首部信息的工作可以由 setRequestHeader完成
参数header: 首部的名字; 参数value:首部的值。
如果用 POST 请求向服务器发送数据,需要将 “Content-type” 的首部设置为 “application/x-www-form-urlencoded”.它会告知服务器正在发送数据,并且数据已经符合URL编码了。
该方法必须在open()之后才能调用
完整的 Ajax 的 POST 请求示例:
用 XMLHttpRequest 的方法可向服务器发送请求。在 Ajax 处理过程中,XMLHttpRequest 的如下属性可被服务器更改:
readyState
status
responseText
responseXML
readyState
readyState 属性表示Ajax请求的当前状态。它的值用数字代表。
0 代表未初始化。 还没有调用 open 方法
1 代表正在加载。 open 方法已被调用,但 send 方法还没有被调用
2 代表已加载完毕。send 已被调用。请求已经开始
3 代表交互中。服务器正在发送响应
4 代表完成。响应发送完毕
每次 readyState 值的改变,都会触发 readystatechange 事件。如果把 onreadystatechange 事件处理函数赋给一个函数,那么每次 readyState 值的改变都会引发该函数的执行。
readyState 值的变化会因浏览器的不同而有所差异。但是,当请求结束的时候,每个浏览器都会把 readyState 的值统一设为 4
status
服务器发送的每一个响应也都带有首部信息。三位数的状态码是服务器发送的响应中最重要的首部信息,并且属于超文本传输协议中的一部分。
常用状态码及其含义:
404 没找到页面(not found)
403 禁止访问(forbidden)
500 内部服务器出错(internal service error)
200 一切正常(ok)
304 没有被修改(not modified)(服务器返回304状态,表示源文件没有被修改 )
在 XMLHttpRequest 对象中,服务器发送的状态码都保存在 status 属性里。通过把这个值和 200 或 304 比较,可以确保服务器是否已发送了一个成功的响应
responseText
XMLHttpRequest 的 responseText 属性包含了从服务器发送的数据。它是一个HTML,XML或普通文本,这取决于服务器发送的内容。
当 readyState 属性值变成 4 时, responseText 属性才可用,表明 Ajax 请求已经结束。
responseXML
如果服务器返回的是 XML, 那么数据将储存在 responseXML 属性中。
只用服务器发送了带有正确首部信息的数据时, responseXML 属性才是可用的。 MIME 类型必须为 text/xml
AJAX开发框架
AJAX实质上也是遵循Request/Server模式,所以这个框架基本的流程是:
对象初始化
发送请求
服务器接收
服务器返回
客户端接收
修改客户端页面内容。
只不过这个过程是异步的。
A、初始化XMLHttpRequest对象
function createXmlHttpRequest(){
var xmlhttp = null;
try{
//Firefox, Opera 8.0+, Safari
xmlhttp=new XMLHttpRequest();
}catch(e){//IEIE7.0以下的浏览器以ActiveX组件的方式来创建XMLHttpRequest对象
var MSXML =
['MSXML2.XMLHTTP.6.0','MSXML2.XMLHTTP.5.0',
'MSXML2.XMLHTTP.4.0','MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP','Microsoft.XMLHTTP'];
for(var n = 0; n < MSXML.length; n ++){
try{
xmlhttp = new ActiveXObject(MSXML[n]);
break;
}catch(e){}}
}
return xmlhttp;
}
B、指定响应处理函数
指定当服务器返回信息时客户端的处理方式。只要将相应的处理函数名称赋给XMLHttpRequest对象的onreadystatechange属性就可以了.比如:
XMLHttpReq.onreadystatechange = processResponse;
注意:这个函数名称不加括号,不指定参数。也可以用Javascript函数直接量方式定义响应函数。比如:
XMLHttpReq.onreadystatechange = function() { };
// 处理返回信息的函数
function processResponse() {
}
C、发出HTTP请求
向服务器发出HTTP请求了。这一步调用XMLHttpRequest对象的open和send方法。
http_request.open('GET', 'http://www.example.org/some.file', true);
http_request.send(null)
按照顺序,open调用完毕之后要调用send方法。send的参数如果是以Post方式发出的话,可以是任何想传给服务器的内容。
注意:如果要传文件或者Post内容给服务器,必须先调用setRequestHeader方法,修改MIME类别。如下:
http_request.setRequestHeader(“Content-Type”,”application/x-www-form-urlencoded”);
这时资料则以查询字符串的形式列出,作为send的参数,例如:
name=value&anothername=othervalue&so=on
发出Http请求的代码
//发送请求
function sendRequest(){
//获取文本框的值
var chatMsg=input.value;
var url="chatServlet.do?charMsg="+chatMsg;
//建立对服务器的调用
XMLHttpReq.open("POST",url,true);
//设置MiME类别,如果用 POST 请求向服务器发送数据,
//需要将"Content-type" 的首部设置为 "application/x-www-form-urlencoded".
//它会告知服务器正在发送数据,并且数据已经符合URL编码了。
XMLHttpReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//状态改变的事件触发器,客户端的状态改变会触发readystatechange事件,
//onreadystatechange会调用相应的事件处理函数
XMLHttpReq.onreadystatechange=processResponse;
//发送数据
XMLHttpReq.send(null);
}
D、处理服务器返回的信息
处理响应处理函数都应该做什么。
首先,它要检查XMLHttpRequest对象的readyState值,判断请求目前的状态。参照前文的属性表可以知道,readyState值为4的时候,代表服务器已经传回所有的信息,可以开始处理信息并更新页面内容了。如下:
if (http_request.readyState == 4) {
// 信息已经返回,可以开始处理
} else {
// 信息还没有返回,等待
}
服务器返回信息后,还需要判断返回的HTTP状态码,确定返回的页面没有错误。所有的状态码都可以在W3C的官方网站上查到。其中,200代表页面正常。
if (http_request.status == 200) {
// 页面正常,可以开始处理信息
} else {
// 页面有问题
}
XMLHttpRequest对成功返回的信息有两种处理方式:
responseText:将传回的信息当字符串使用;
responseXML:将传回的信息当XML文档使用,可以用DOM处理。
//处理返回信息的函数
function processResponse(){
if(XMLHttpReq.readyState==4){ //判断对象状态 4代表完成
if(XMLHttpReq.status==200){ //信息已经成功返回,开始处理信息
document.getElementById("chatArea").value=XMLHttpReq.responseText;
}
}
}
数据格式摘要
在服务器端 AJAX 是一门与语言无关的技术。在业务逻辑层使用何种服务器端语言都可以。
从服务器端接收数据的时候,那些数据必须以浏览器能够理解的格式来发送。服务器端的编程语言一般以如下 3 种格式返回数据:
XML
JSON
HTML
XML格式
优点:
XML 是一种通用的数据格式。
不必把数据强加到已定义好的格式中,而是要为数据自定义合适的标记。
利用 DOM 可以完全掌控文档。
缺点:
如果文档来自于服务器,就必须得保证文档含有正确的首部信息。若文档类型不正确,那么 responseXML 的值将是空的。
当浏览器接收到长的 XML 文件后, DOM 解析可能会很复杂
JSON格式
JSON(JavaScript Object Notation)一种简单的数据格式,比xml更轻巧。JSON是JavaScript原生格式,这意味着在JavaScript中处理JSON数据不需要任何特殊的API或工具包。
JSON的规则很简单:对象是一个无序的“‘名称/值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后跟一个“:”(冒号);“‘名称/值’对”之间使用“,”(逗号)分隔。
规则如下:
1)映射用冒号(“:”)表示。名称:值
2)并列的数据之间用逗号(“,”)分隔。名称1:值1,名称2:值2
3) 映射的集合(对象)用大括号(“{}”)表示。{名称1:值1,名称2:值2}
4) 并列数据的集合(数组)用方括号(“[]”)表示。
[
{名称1:值,名称2:值2},
{名称1:值,名称2:值2}
]
5 元素值可具有的类型:string, number, object, array, true, false, null
JSON 示例
JSON 用冒号(而不是等号)来赋值。每一条赋值语句用逗号分开。整个对象用大括号封装起来。可用大括号分级嵌套数据。
对象描述中存储的数据可以是字符串,数字或者布尔值。对象描述也可存储函数,那就是对象的方法。
解析JSON
JSON 只是一种文本字符串。它被存储在 responseText 属性中
为了读取存储在 responseText 属性中的 JSON 数据,需要根据 JavaScript 的 eval 语句。函数 eval 会把一个字符串当作它的参数。然后这个字符串会被当作 JavaScript 代码来执行。因为 JSON 的字符串就是由 JavaScript 代码构成的,所以它本身是可执行的
代码实例:
处理JSON
例子一:
<script language="JavaScript">
var people ={"firstName": "Brett", "lastName":"McLaughlin",
"email": "brett@newInstance.com" };
alert(people.firstName);
alert(people.lastName);
alert(people.email);
</script>
例子二:
<script language="JavaScript">
var people =[
{"firstName": "Brett","email": "brett@newInstance.com" },
{"firstName": "Mary","email": "mary@newInstance.com" }
];
alert(people[0].firstName);
alert(people[0].email);
alert(people[1].firstName);
alert(people[1].email);
</script>
例子三:
<script language="JavaScript">
var people ={
"programmers":
[
{"firstName": "Brett", "email": "brett@newInstance.com" },
{"firstName": "Jason", "email": "jason@servlets.com" }
]
};
window.alert(people.programmers[0].firstName);
window.alert(people.programmers[1].email);
</script>
例子四:
<script language="JavaScript">
var people ={
"programmers": [
{ "firstName": "Brett", "email": "brett@newInstance.com" },
{ "firstName": "Jason", "email": "jason@servlets.com" },
{ "firstName": "Elliotte", "lastName":"Harold", "email": "elharo@macfaq.com" }
],
"authors": [
{ "firstName": "Isaac", "genre": "science fiction" },
{ "firstName": "Tad", "genre": "fantasy" },
{ "firstName": "Frank", "genre": "christian fiction" }
],
"musicians": [
{ "firstName": "Eric", "instrument": "guitar" },
{ "firstName": "Sergei", "instrument": "piano" }
]};
window.alert(people.programmers[1].firstName);
window.alert(people.musicians[1].instrument);
</script>
例子五
<script language="JavaScript">
var people ={
"username":"mary",
"age":"20",
"info":{"tel":"1234566","celltelphone":788666},
"address":[
{"city":"beijing","code":"1000022"},
{"city":"shanghai","code":"2210444"}
]
};
window.alert(people.username);
window.alert(people.info.tel);
window.alert(people.address[0].city);
</script>
JSON 小结
优点:
作为一种数据传输格式,JSON 与 XML 很相似,但是它更加灵巧。
JSON 不需要从服务器端发送含有特定内容类型的首部信息。
缺点:
语法过于严谨
代码不易读
eval 函数存在风险
解析 HTML
HTML 由一些普通文本组成。如果服务器通过 XMLHttpRequest 发送 HTML, 文本将存储在 responseText 属性中。
不必从 responseText 属性中读取数据。它已经是希望的格式,可以直接将它插入到页面中。
插入 HTML 代码最简单的方法是更新这个元素的 innerHTML 属性。
HTML 小结
优点:
从服务器端发送的 HTML 代码在浏览器端不需要用 JavaScript 进行解析。
HTML 的可读性好。
HTML 代码块与 innerHTML 属性搭配,效率高。
缺点:
若需要通过 AJAX 更新一篇文档的多个部分,HTML 不合适
innerHTML 并非 DOM 标准。
对比小结
若应用程序不需要与其他应用程序共享数据的时候, 使用 HTML 片段来返回数据时最简单的
如果数据需要重用, JSON 文件是个不错的选择, 其在性能和文件大小方面有优势
当远程应用程序未知时, XML 文档是首选, 因为 XML 是 web 服务领域的 “世界语”
案例:省份与城市的联动下拉列表框
效果演示。
联动下拉:纯静态数据的html方式
实验步骤:
演示程序运行的效果。
编写一个静态province.html页面,其中使用一个二维数组来装载所有数据,然后分析和编码实现省份与城市的联动下拉列表框。
编写一个静态jsonProvince.html页面,其中使用JSON对象方式来装载所有数据,然后分析和编码实现此种数据格式下的省份与城市的联动下拉列表框。
对于静态网页显示省份和城市,首先要考虑用什么样的数据结构来存储所有信息,每个选项信息包括名称和id,如果单独用一个数组保存各个省份,再用一个数组保存各个省份下的城市,是可以的,但是需要在两个地方维护数据,要保持数据的同步。提示:由于学员基础的缘故,对于此部分的讲解,一定要写一些示意代码来进行说明。
与其如此,还不如用一个数组把省份和省份下的城市全部保存起来。
没有唯一和最好的数据存储结构,只有最适合你和你认为不错的数据存储结构,首先考虑的是把功能实现出来,其次才是考虑哪种方式更优雅和方便些。在讲课时,对于二维数组和json数据结构,都是先把写好的静态province.html页面中的各个函数删除掉,然后让同学们自己去完成这些函数,这样同学们的学习兴致很高,确实也能让同学们马上就感到有收获和有进步。
在编写触发的显示某个省份下面的城市的js函数时,先简单地alert一下选中的省份的值。
//下面代码在firefox下不成功!
//document.getElementById("provinceId").add(optProvince);
//document.getElementById("provinceId").options.remove(i);
//下面代码在firefox下成功!
document.getElementById("provinceId").options.add(optProvince);
document.getElementById("provinceId").remove(i);
联动下拉:动态生成数据的方式
实验步骤:
编写一个ListProvinceAction的Action和相应的province.jsp页面,留出数据待填充,大家清晰看到后面的任务就是生成出数据。
创建代表省份与城市的province和City实体类,然后将相应的实体对象存放在一个单例的MemoryDao中,用一个ArrayList集合存储所有Province对象,Province对象中保存有一个City对象的集合,在MemoryDao中构建出各个对象及关系。
创建ProvinceService类获取所有省份列表和CityService类获取某个省份下的所有城市。
在ListProvinceAction中编写拼凑出JSP页面所需要的那个数组字符串。
编写一个ListJsonProvinceAction的Action和相应的jsonProvince.jsp页面,以生成使用JSON对象来装载所有数据,为了方便拼凑JSON格式的字符串,在各个实体对象中覆盖toString方法返回自身的JSON格式字符串,这要比在外面最后统一转换成一个JSON字符串的做法要优雅很多,这种分而治之的思想使得程序健壮且易于维护。
编写一个ListJsonProvince2Action的Action,快速演示和说明一下如何在其内部用JSONObject与JSONArray工具里来完成json字符串的生成工作。
使用StringBuilder拼凑字符串时,讲解了其与StringBuffer的区别,线程安全好比人走路,如果总是考虑怕与别人或汽车相撞,那走路速度慢,如果不花精力去注意别人,只管埋头走路,速度肯定要快。如果知道是单行通道走,那么就可以不去考虑别人,只管自己一个人埋头走路好了。
当要将ArrayList中的元素转换成js的json格式的字符串输出时,不需要自己再去拼凑字符串,可以直接调用ArrayList的toString方法,该方法输出的字符串正好就符合json数组的语法规范。
联动下拉:用Ajax方式实现的思路分析
传统方式与Ajax实现方式的对比与选择:
好比饭馆上菜的方式:一种是先让用户等较长时间,最后一下子将所有的菜全部上上来,一般的火锅店都是这么做的,还有一种方式就是做好一盘菜就上一盘菜,用户等待的时间较短,但送餐服务员要跑好多次,一般的家常菜饭店都是这么做的。
如何选择:传统方式是一下子把所有数据搞到手,以后只是用js把到手的数据显示出来即可,第一次得到数据的时间比较长,如果这个时间长得影响了用户的感受,那就考虑用ajax进行改进,否则,可以直接使用传统方式。
如果将传统方式改造为ajax方式的总结:将原有的一个jsp页面改为两个jsp页面来实现,第一个页面为那些固定不变的内容和javascript代码,第二个页面为那些要改变的区域的代码,因此原来用一个action或servlet实现的代码要改变为用两个action或servlet来实现,这称为二步视图法。
实现的思路剖析:
第一次要获得一个页面,浏览器以后一直显示这个页面,以后的每个操作都触发这个页面内部的一个javascript函数,再由这个javascript函数去发请求和处理回应结果。
第一次获得的页面应该包含什么信息?页面的初始内容和javascript函数。
以后的每次请求要获得什么样的结果?
如果本应用只显示几个省份及各个省份下面的少数几个城市,则可以用传统方式。如果本应用是要显示出全国所有省份下面的所有城市,涉及的数据量就比较大了,如果要从数据库中一下子查出31个省和它们下面的所有城市,通常要涉及32条sql语句,花费的时间较长;如果改为Ajax按需取数据的方式,即选择哪个省份后,再去取该省份下的城市,这样,第一次展现的速度就较快。给大家看看腾讯的网吧充值界面图。
联动下拉:用json数据传输格式的Ajax方式实现
步骤:
编写AjaxListProvince的Action和ajaxProvince.jsp页面,在一个下拉列表框中列出所有省份,编写触发和发送获取某个省份下面的城市的请求消息的代码,先简单的alert一下响应结果。
根据客户端的请求信息,分析和编写一个ListCities的Action调用CityService类获取某个省份下的所有城市,Action返回一个包含有某个省份下的所有City的json格式的数组。
在ajaxProvince.jsp页面中编写处理返回结果的javascript代码,使用javascript自己的eval方法处理json字符串。
改进为用prototype自己的json支持来处理返回的json串,包括扩展的String.evalJSON()方法和transport.responseJSON属性这两种方式。
由于本例子程序返回的城市对象很简单,可以使用一个map来表示某个省份下的所有City信息,同时简化客户端的javascript代码。
编写jsp页面,因为没有涉及<html:form>表单,所以无法使用struts的html:optionCollections标签来生成下拉列表框的option选项,只能使用jstl标签。
注意:改为ajax方式来实现时,传递给事件处理函数中的参数不再是选择项的索引号,而是选择项的值了,因为每次都要从服务器端来获取某个省份下的城市集合,传给服务器的参数是省份的id值。
浏览器一上来要发两次请求,第一次得到省份,第二次得到当前选中的省份的城市信息,而不是一次就得到省份和第一个省份的城市信息,一个模块专门负责省份,不要让它既处理省份,又处理城市。
学员在做第二步时,做好ListCityAction后,先用浏览器测试一下,然后用ajax调用,在ajax调用的回调函数中,先alert一下返回的这个串,最后再把这个串转换成为json对象。
在baidu中搜素”jquery select 添加选项”
学员写的一段代码:
for(var i=0;i<data.length;i++){
var op=$("<option>").attr('id',data[i][1]).html(data[i][0]);
$("#provinceId").append(op);
}
分别用jquery和protoye做一遍。Jquery的json支持做一遍。Onchange事件用prototype和jquery提供的方式来注册。对于清楚下拉列表框中的所有选项,可以使用jquery对象的empty函数或者让其innerHTML为空。用jquery时,最好是把事件处理的注册代码放在js中写,而不是放在html中,所谓的内容与行为相分离,术语借鉴了内容与表现相分离的说法。
有学员提到了如何自己实现缓存,借此机会正好把缓存给实现了一下:
可以用数组方式实现,用其id值作为数组元素的索引,如果id=99,测试数组的长度一下就变成了100,所有这样不合理,代码如下:
var data=[];
data[pid] = cities;
alert(data.length);
于是想到改为用对象方式来实现:
var data={};
data[pid] = cities;
for(var a in data)
{
alert(a + “:” + data[a]);
}
后来用数组进行循环,发现实际上也只有添加进去的几个元素,这里数组与对象的区别似乎就是对象没有length属性。
有问题:$("#provinceId").change(fillCity($(this).val()));
正确: $("#provinceId").change(function(){
fillCity($(this).val());
});
用jquery.each方法迭代出来元素是htmlelement类型,而不是jquery对象。可以使用nodeName来查看htmlelement元素名。
联动下拉:用xml数据传输格式的Ajax方式实现
步骤:
编写一个ListCity2的Action,返回一个包含有某个省份下的所有City的xml文档。
直接用浏览器访问ListCity2,测试查看返回的xml文档内容是否正确。
客户端页面改为ajaxProvince2.jsp,在其中编写解析xml文档内容和将结果显示在下拉列表框中的代码,并且将AjaxListProvince配置为/ AjaxListProvince2.do
今天用jquery做时,只要在发送请求时,将dataType:’xml’,那么,jquery就回将xml文档转换成Document对象,并将该Document对象作为参数传递给回调函数,也就是说,此时的回调函数接收的参数就是Document对象。
/*拼出如下的数据格式,该如何做呢?
*
<cities>
<city>
<id></id>
<name></name>
</city>
<city>
<id></id>
<name></name>
</city>
</cities>
*/
Jsp就是拼凑大段字符串的技术,是模板技术,大量固定不变的串中偶尔要夹杂一点变化的数据,这就是jsp的用武之地。
jsp就是为了方便拼凑大量文本串而推出的技术,我们为何放着这么好的东西不用呢?
不需要写AjaxListProvince2Action,只需要把AjaxListProvinceAction再配置一遍,这正是mvc的优点,控制器和模型不变,可轻松改变视图(view)
联动下拉:返回整个下拉列表框的HTML代码
步骤:
编写一个ListCity3的Action,返回一个包含有某个省份下的所有City的下拉列表框的html代码。
直接用浏览器访问ListCity3,测试查看返回的html代码是否正确。
客户端页面改为AjaxProvince3.jsp 和在其中定义一个<div>元素来容纳服务器返回的下拉列表框,并且将AjaxListProvince配置为/ AjaxListProvince3.do。
为什么有了前面两种非常优雅的做法,还要讲那些不优雅和很土的做法呢?因为外面有些公司用的就是土方法,真正用优雅方式的是很牛的好公司,这样的好公司并不多,外面的公司为什么要用土方法呢?好比你读了小学一年级就可以挣钱了,以后一直忙于挣钱,就没必要去读博士了,所以一直会用小学一年级的方式工作下去。虽然你读博士后,掌握了只有博士能挣到的钱的本领,挣钱的方式可能更优雅,但有时候,博士也要用小学一年级的方式去挣钱,例如,有的博士是房产公司的经理,但他挣钱的方式很原始,完全是小学一年级水平的方式,只要会送礼和会喝酒就可以,读博士掌握的本领全用不上也是可能的。
大家对有的Action用response.getWriter()方法直接输出结果,对有的Action却跳转到一个jsp页面上的关系与区别总是搞不太清楚,问这问题的人都是属于基础比较不错,已经开动了脑筋的学员。看来大家对jsp的根本作用和工作原理了解得还是不够好。
返回的html代码片段前面有几个空格和换行,导致显示的城市下拉列表框与省份下来列表框之间有很大的空袭,我是让返回的html代码前时尽量去掉前面的空格,这时候应该用prototype或jquery提供的去掉两端空格的方法来完成。
联动下拉:返回向下拉列表框填充选项的js代码
步骤:
编写一个ListCity4的Action,返回一个用于将某个省份下的所有City添加进下拉列表框的javascript代码。
直接用浏览器访问ListCity4,测试查看返回的javascript代码是否正确。
客户端页面改为AjaxProvince4.jsp 和使用eval方法执行服务器端返回的javascript代码,并且将AjaxListProvince配置为/ AjaxListProvince4.do。
这种方式既不优雅,也不简单,但有人用,我觉得完全是无用,毫无价值。
今天用jquery做时,首先用eval(“(“ + msg + “)”)执行时,ie总是报告错误,说缺少),换成firefox查看,提示错误如下:
missing ) in parenthetical
document.getElementById('cityId').options.add(new Option('武汉',3));\n
原来是在返回的字符串最后多了个\n,把整个串用()括起来后,就相当于一个完整的字符串中间换行铡成两段,这种语法是不行的。
于是,去掉(),即代码改为eval(msg),再运行,结果就正常了。
最后读jquery的Ajax函数的文档,发现dataType选项有一个设置值为script,于是设置了这个选项,发现city的填充效果被double,
这说明将dataType设置为script后,jquery会自动帮我们执行javascript代码,在这种情况下,不写success回调函数也可以看到运行效果。
-------------------------------------------------------------------------------------------
最后要给大家总结一下,这样大家思路就不混乱了:对于用4种不同方式返回城市,第一步显示省份的页面需要做几个?返回省份信息的前置Action需要写几个。
对于第二步返回城市信息,除了json之外的3种方式需要做几个页面?需要做几个返回城市信息的Action?为什么返回json数据时的Action与返回其他数据时的Action不同?
案例扩展:多级地区的动态展现
实验步骤:
演示和分析程序运行的效果。(每选择一个区域,则显示一个包含有其所有子区域的下拉列表框,并清除其他不相关的下拉列表框;当选择了某个没有子区域的选项后,不再显示出新的下拉列表框,而是显示该区域的网吧)
分析数据库表结构该如何设计,并执行预备的脚本文件创建表结构和数据。
分析JSP页面该如何设计:
第一个返回的页面应该包含哪些内容?
以后每次请求获得的结果是什么?返回的结果怎样展现在返回的第一个页面中?动态生成出下拉列表框和将下拉列表框追加到其他列表框后面,或者提前预定义若干span元素,将新增的下拉列表框添加到相应的span元素中。如果要实现用表格来显示某个区域的网吧,服务器端返回的要么是select元素,要么是table元素,客户端要判断结果的类型。
编码实现:先用span的方式实现;再用prototype的删除和添加功能。
我是一个通用的产品,要卖给不同的公司,不同公司的级别分类的层次是不一样,我们通常应该允许无限极分类,还有书的分类,这样的系统的数据库该如何设计。是设计无限个表,还是设计一个表。无限级分类应用很多,例如论坛的版面,帖子的回复等等。
id,name,parentid
1, 集团,0
2. 一公司, 1
3. 二公司, 1
4.三公司,1,3
查询集团下的直接子部门:select * from dept where parentid=0;
查询一公司下的直接子部门:select * from dept where parentid=1;
为一公司添加子部门:insert into detp values(..,1)
父亲可以知道自己的所有孩子,孩子可以知道自己的父亲,这就很满足需求了嘛!
网吧为什么要有一个字段指向所属的区别?因为要列出一个区域下的所有网吧,如果没这需求,当然也就不需要那个字段了。
这里的第一步不需要显示出所有省份,因为显示省份的方式和显示子地区的方式完全一样,所以,显示省份信息借助第二部操作来完成,只是第一步做完后立即进入第二步弄出省份。
先不做网吧部分,等做完地区后,让学员看看使用dao查询网吧只是复制工作,环境搭建只是一次性的工作,只要环境搭建好了,开发过程就是这样的拷贝和复制。
对于采用span元素的实现方式,每个下拉列表框都要用同一个函数来实现,这个函数需要接受两个参数:要显示的地区的父级id(即列表框要显示哪个id地区下的子地区),得到显示的结果放在哪个span元素里。先完全用手工编写方式分析服务器每次返回的下拉列表框的html代码怎样?再分析用jsp如何生成,由于返回的结果既要有某个id的子地区,又要在生成的下拉列表框的onchange事件中指定下一级地区列表框所放置的span元素,所以,传递给服务器的参数也要有两个。如果不给服务器传递当前span元素的id值,而是等服务器返回一个结果后,然后客户端再用js代码来指定下一个span元素的id值,这样更好,因为服务器端不用考虑客户端的页面情况了,即服务器端的代码不用随客户端页面的改动(地区级别)而改变,这中方式可以留给同学们自己做。对于这种情况,js代码中还要判断服务器返回的是<select>还是<table>,是table则往网吧的<span>中填写。
每次添加新的下拉列表框前,都是删除原来的当前要生成的下拉列表框以后的所有列表框和表格,再更新当前要生成的下拉列表框。这里操作的参数是指当前要生成的下拉列表框所在的span元素的id。
对于采用prototype的添加和删除功能,得到所有弟弟并删除,增加新弟弟。有一点奇怪的是,使用$(sibling).remove方法,无法完成删除功能,但使用Element.remove(sibling)却可以,其中具体的差别在哪,还没有找出来。另外每次生成一个下拉列表框,然后再删除,再添加,下次生成的下拉列表框与上次生成的下拉列表框有一定的距离,这就是因为返回的<select>标签前面有空格导致的。也可以利用Prototype提供了Element.cleanWhiteSpace方法来清除所有空格。
每次添加新的下拉列表框前,如果不是第一个(通过调用函数时,有无传递当前元素这个参数来判断),则删除当前发生事件的下拉列表框以后的弟弟(包括所有后续列表框和表格),再新增一个下拉列表框,如果是第一个,则直接新加。这里的参数是当前正在发生事件的下拉列表框。
设计多级分类,可以使用字符串类型的id,用id的值来表示级别和隶属关系,例如,001,001001,001002,001001001等。这是我们十年前总是乐意当着一个经验向大家讲解的知识,现在由于掌握的新技术太多了,都忘记讲这些以前的经验了,可能是人都有点喜新厌旧的习惯吧。其实,对于新手来说,我们这些老多年前的经验仍然是很有价值的。
一道面试题:把span1变成span2,把span2变成span3,把span3变成span4,把span5变成span6