因此,您可以在这里开始学习有关AJAX的所有知识。 但是,这到底是什么? 术语AJAX是指用于创建动态交互式Web内容的技术的松散组合。
术语AJAX最初由Adaptive Path的Jesse James Garrett在他的文章AJAX:Web应用程序的新方法中提出,是“异步JavaScript和XML”的缩写。 这有点麻烦,但这只是在描述一种技术,该技术使用JavaScript从Web服务器刷新页面的内容,而不必重新加载整个页面。 这与传统的网页更新方法不同,后者需要浏览器刷新整个页面才能显示对内容的任何更改。
类似的技术以一种或另一种形式出现(通常是在一些聪明的黑客的帮助下实现的)已经有一段时间了。 但是,随着浏览器中XMLHttpRequest类的可用性不断提高,引人入胜的术语AJAX的出现以及诸如Google Maps , Gmail , Backpack和Flickr之类的许多引人注目的示例的出现,这些类型的高度交互性的Web得以实现。应用程序开始在开发世界中受到关注。
随着术语AJAX变得越来越广泛,其定义已扩展为更笼统地指代基于浏览器的应用程序,这些应用程序的行为比老式Web应用程序动态得多。 AJAX Web应用程序的这种新形式更广泛地使用了交互技术,例如就地编辑文本,拖放和CSS动画或转换,以实现用户界面内的更改。 本教程将解释这些技术,并向您展示如何开发自己的AJAX Web应用程序。
本教程摘自我的新书《 构建自己的AJAX Web应用程序》 。 在这里介绍的三章中,我们将讨论AJAX的基础知识,并学习它的原理,然后再深入研究XMLHttpRequest的奇妙世界。 在研究了它的内部功能,提出请求并异步更新我们的应用程序页面之后,我们开始开发我们的第一个真正的AJAX应用程序。
这将是一个很大的旅程,所以我希望您已经准备好冒险了! 如果您想阅读这些章节以离线阅读,请下载它们的.pdf版本 。 但是现在,让我们扎根AJAX。
第1章AJAX:概述
他逃跑了,白痴!
派遣战争火箭阿贾克斯!
带回他的身体!
—卡拉·将军,Flash戈登
AJAX Web应用程序
对于许多Web开发项目而言,AJAX可能是一个很好的解决方案-它可以使Web应用程序得以增强并接管以前几乎完全由桌面应用程序占据的许多领域。
尽管如此,请务必牢记AJAX并不是一种魔术般的尘埃,您可以将其撒在您的应用程序上以使其既时髦又酷。 像任何其他新开发技术一样,AJAX并不容易被滥用,并且唯一可怕的是,一个糟糕的,执行不佳的AJAX Web应用程序比一个可怕的,陈旧的老式Web应用程序还要糟糕。
当您以正确的方式将其应用到Web应用程序的正确部分时,AJAX可以显着增强用户对应用程序的体验。 AJAX可以提高应用程序的交互性和速度,最终使该应用程序更容易,更有趣且更直观地使用。
通常,AJAX应用程序被描述为“像浏览器中的桌面应用程序”。 这是一个相当准确的描述-AJAX Web应用程序比传统的老式Web应用程序具有更高的响应速度,并且它们可以提供类似于桌面应用程序的交互级别。
但是,AJAX Web应用程序仍然是远程应用程序,其行为不同于可以访问本地存储的桌面应用程序。 作为AJAX开发人员,您的工作之一就是设计出响应迅速且易于使用的应用程序,尽管该应用程序与远程服务器之间必须进行通信。 幸运的是,AJAX工具箱为您提供了许多出色的技术来实现这一目标。
糟糕的过去
除了提供简单的静态HTML页面之外,第一个Web开发任务是使用后端数据存储中的数据在Web服务器上动态构建页面的技术。
在Web开发的“糟糕年代”中,创建这种动态的,数据库驱动的内容的唯一方法是使用CGI脚本(很可能是用Perl编写)或某种方式在服务器端构造整个页面。可以解释脚本语言的服务器组件(例如Microsoft的Active Server Pages)。 即使对该页面进行一次更改,也需要从浏览器到服务器的往返行程-只有这样,新内容才能呈现给用户。
在那些日子里,Web应用程序用户界面的常规模型是用户可以填写并提交给服务器的Web表单。 服务器将处理提交的表单,然后将一个全新的页面发送回浏览器以进行显示。 因此,例如,完成基于Web的多步骤“向导”将要求用户为每个步骤提交一个表单,从而促使浏览器和服务器之间进行往返。
当然,这在静态网页上是一个巨大的进步,但是与向最终用户提供真正的“应用程序”体验相差甚远。
史前AJAX
早期的Web开发人员在努力创建更具响应性和交互性的Web应用程序时,立即开始寻找技巧来扩展该简单的基于表单的模型的功能。 这些黑客攻击虽然是临时性和粗暴的,但它们是Web开发人员迈向当今AJAX应用程序中所看到的那种交互性的第一步。 但是,尽管这些技巧和变通办法通常可以提供可使用的有效解决方案,但最终的代码并不是一见钟情。
嵌套框架集
解决不得不重新加载整个页面以显示其内容的最小更改的问题的一种方法是在其他框架集中嵌套框架集(通常是几层深度)的可怕方法。 这项技术允许开发人员仅更新屏幕的选定区域,甚至可以模仿选项卡式导航界面的行为,其中用户单击屏幕某一部分的选项卡会更改另一区域的内容。
这种技术导致大量可怕的,无法维护的代码,并且页面上充斥着诸如EmployeeEditWizardMiddleLowerRight.asp之类的名称。
隐藏的iframe
在诸如Internet Explorer 4之类的浏览器中添加iframe
,使事情减轻了很多麻烦。 完全隐藏iframe的能力导致了另一种巧妙的方法:开发人员将使用隐藏的iframe向服务器发出HTTP请求,然后使用JavaScript和DHTML将内容插入页面。 这提供了许多与现代AJAX相同的功能,包括无需重新加载页面即可从表单提交数据的功能-通过将表单提交给隐藏的iframe而实现的。 服务器将结果返回到iframe
,页面的JavaScript可以在其中访问它。
这种方法的最大缺点(毕竟它毕竟是黑客),这是在主文档和iframe中的文档之间来回传递数据的烦人负担。
远程脚本
另一种类似AJAX的早期技术(通常称为远程脚本编制)涉及设置<script>
标记的src
属性,以加载包含动态生成的JavaScript的页面。
这样做的好处是比隐藏的iframe
骇客更干净,因为服务器上生成的JavaScript会直接加载到主文档中。 但是,使用此技术只能进行简单的GET请求。
是什么让AJAX酷起来
这就是为什么AJAX开发是Web开发如此巨大的飞跃的原因:Web开发人员不必与单个的庞大文件一起发送所有内容,而是等待服务器发回新页面进行渲染,而与Web开发人员进行通信服务器以较小的块为单位,并根据服务器对这些请求的响应有选择地更新页面的特定区域。 这是AJAX首字母缩写词中异步这个词的起源。
通过考虑异步系统的对立面(即同步系统),可能最容易理解它。 在同步系统中,一切都按顺序进行。 如果赛车是一个同步系统,那将是一件非常乏味的事情。 首先在并列发车的赛车将是终点线的第一辆,然后是第二名的赛车,依此类推。 不会超车,如果汽车发生故障,后面的交通将被迫停车,等待修理工修理。
传统的Web应用程序使用同步系统:您必须等待服务器向您发送系统的第一页,然后才能请求第二页,如图1.1所示。
图1.1。 传统的Web应用是同步系统
异步赛车将更加令人兴奋。 处于杆位的赛车可能会在第一个弯道被超越,而从格网背面发车的赛车可以穿越场地,并越过终点线获得第三名。 来自AJAX应用程序中浏览器的HTTP请求正是以这种方式工作的。 正是这种能力可以根据需求向服务器发出许多小的请求,这使得AJAX开发非常酷。 图1.2显示了向Web服务器发出异步请求的AJAX应用程序。
图1.2。 AJAX Web应用程序是一个异步系统
最终结果是使应用程序具有更高的响应速度,因为用户花费在等待处理请求上的时间大大减少,而不必等待整个新的网页通过网络传输并由其浏览器呈现,才可以查看结果。
AJAX技术
用于构建AJAX Web应用程序的技术包含许多不同的编程领域,因此AJAX开发既不像常规应用程序开发那么简单,也不像老式Web开发那么容易。
另一方面,AJAX开发包含许多不同技术的事实使它变得更加有趣和有趣。 以下是可以共同制作AJAX Web应用程序的技术的简要列表:
- XML格式
- W3C DOM
- 的CSS
- XMLHttpRequest
- 的JavaScript
在本章的其余部分中,我们将介绍每种技术,并讨论它们在AJAX Web应用程序中扮演的角色。
数据交换和标记:XML
XML(XML代表可扩展标记语言-从来没有人称其为教科书之外的语言。)是AJAX获得其字母“ X”的地方。 这是幸运的,因为技术首字母缩略词如果包含字母“ X”,就会自动被视为凉爽得多。 (是的,我在开玩笑!)
数据交换Lingua Franca
XML通常用作异步HTTP请求中使用的主要数据格式,该异步HTTP请求在AJAX应用程序中的浏览器和服务器之间进行通信。 这种角色发挥了XML作为中立且相当简单的数据交换格式的优势,并且还意味着如果需要,可以相对容易地重用或重新格式化内容。
当然,还有许多其他方式可以格式化数据,以便在浏览器和服务器之间轻松交换(例如CSV(逗号分隔值),JSON(JavaScript对象表示法)或简单的纯文本),但是XML是其中一种最常见的。
XML作为标记
AJAX应用程序中的网页包含XHTML标记,实际上只是XML的一种形式。 XHTML作为HTML的后继者,与它非常相似。 任何熟悉老式HTML的开发人员都可以轻松选择它,但它拥有有效XML的所有优点。 使用XHTML有许多优点:
- 它提供了许多用于查看,编辑和验证XML的标准工具和脚本库。
- 它与更新的,与XML兼容的浏览器向前兼容。
- 它可以与HTML文档对象模型(DOM)或XML DOM一起使用。
- 它更容易重新用于非浏览器代理中的查看。
开发社区中一些比较古怪的人坚持认为人们不应该使用XHTML。 他们非常坚信,除非XHTML是实际的XML,否则根本不要使用XHTML,除非它可以与application/xhtml+xml
的正确的HTTP Content-Type
标头一起application/xhtml+xml
( text/xml
和application/xml
也可以) ,尽管它们的描述性较差),但目前对浏览器的支持仍然有限。 (Internet Explorer 6和7完全不支持。)
实际上,您可以将Content-Type
为text/html
XHTML提供给浏览器,因为所有主流浏览器都可以正确渲染所有以text / html形式提供的XHTML文档。 尽管浏览器会将您的代码当作普通的旧HTML对待,但其他程序仍可以将其解释为XML,因此没有实际的理由不使用它来“标记未来”。
如果您不同意我的意见,可以选择使用较旧的HTML 4.01标准进行开发。 这仍然是可行的Web标准,并且是开发Web应用程序时做出的完全合法的选择。
XHTML和这本书
本书中的大多数代码示例都将使用XHTML 1.0 Strict。 iframe元素在Strict中不可用,因此我们使用iframe展示的一些代码示例将是XHTML 1.0 Transitional。
万维网联盟维护有关HTML和XHTML之间差异的常见问题 。
W3C文档对象模型
文档对象模型(DOM)是XML和HTML文档的面向对象的表示形式,并提供用于更改这些文档的内容,结构和样式的API。
最初,特定的浏览器(例如Netscape Navigator和Internet Explorer)提供了不同的专有方法来使用JavaScript处理HTML文档。 DOM源于万维网联盟(W3C)的努力,它提供了一种与平台和浏览器无关的方式来完成相同的任务。
DOM将XML或HTML文档的结构表示为对象层次结构,这是通过标准XML工具进行解析的理想选择。
DOM操作方法
JavaScript在解析和处理文档方面都提供了用于处理这些DOM结构的大型API。 这是对我们在AJAX应用程序中看到的网页进行较小的,逐段的更改的主要方法之一。 (另一种方法只是更改元素的innerHTML
属性。尽管主流浏览器都广泛支持该方法,但该标准在任何标准中均未得到很好的记录。)
DOM事件
DOM的另一个重要功能是,它为JavaScript提供了一种将事件附加到网页上的元素的标准方法。 这使更丰富的用户界面成为可能,因为它使您可以为用户提供与页面进行交互的机会,而不仅仅是简单的链接和表单元素。
拖放功能就是一个很好的例子,该功能使用户可以在屏幕上拖动页面的各个部分,并将它们放到适当的位置以触发特定的功能。 这种功能以前仅在桌面应用程序中存在,但是现在由于DOM而在浏览器中也可以正常使用。
介绍:CSS
CSS(层叠样式表)提供了一种用于控制Web应用程序中用户界面元素外观的统一方法。 您可以使用CSS更改页面外观的几乎所有方面,从字体大小,颜色和间距到元素的位置。
在AJAX应用程序中,CSS的一个很好的用途是提供用户界面反馈(带有CSS驱动的动画和过渡效果),或指示用户可以与之交互的页面部分(更改颜色或触发外观,例如,通过鼠标悬停)。 例如,您可以使用CSS过渡来指示应用程序的某些部分正在等待服务器上正在处理的HTTP请求。
CSS操纵在术语AJAX的更广泛定义中占有重要地位-在各种视觉过渡和效果中以及在拖放和就地编辑功能中。
通讯: XMLHttpRequest
XMLHttpRequest
是一个具有非常易于使用的界面的JavaScript类,它向Web服务器发送HTTP请求以及从Web服务器接收HTTP请求和响应。 XMLHttpRequest
类使真正的AJAX应用程序开发成为可能。 使用XMLHttpRequest
发出的HTTP请求就像浏览器发出加载页面或提交表单的正常请求一样工作,而用户不必离开当前加载的网页。
Microsoft首先在Windows Internet Explorer 5中将XMLHttpRequest
实现为ActiveX对象。 Mozilla项目从1.0版开始,在Mozilla浏览器中提供了JavaScript本地版本和兼容的API。 (当然,它也可以在Firefox中使用。)苹果从1.2版开始向Safari中添加了XMLHttpRequest
。
服务器的响应(XML文档或文本字符串)可以传递给JavaScript以供开发人员认为合适的使用,通常可以更新Web应用程序的某些用户界面。
放在一起:JavaScript
JavaScript是将您的AJAX应用程序结合在一起的粘合剂。 它在AJAX开发中扮演多个角色:
- 控制使用
XMLHttpRequest
发出的HTTP请求 - 根据所使用的数据交换格式,使用DOM操作方法,XSLT或自定义方法来解析从服务器返回的结果
- 通过使用DOM操作方法将内容插入到网页中,通过更新元素的
innerHTML
属性或更改元素的CSS属性,在用户界面中显示结果数据
由于JavaScript在轻量级Web编程中的悠久使用历史(以及缺乏经验的程序员),尽管事实上事实上它是一种完全的功能,但许多传统应用程序开发人员并未将JavaScript视为“严肃的编程语言”。功能强大的动态语言,能够支持面向对象的编程方法。
随着AJAX开发技术扩展了基于浏览器的应用程序的功能和功能,对JavaScript作为“玩具语言”的误解正在迅速改变。 由于AJAX的出现,JavaScript现在似乎正在经历复兴,并且可用于AJAX开发的JavaScript工具包和库的数量爆炸性增长就是事实。
摘要
在本章中,我们快速浏览了AJAX及其技术。 我们研究了开发人员在糟糕的过去必须忍受的一些可怕的编码扭曲,才能创建类似于交互式UI的内容,并且我们看到了AJAX如何在这些方法上提供了巨大的改进。 通过对AJAX构建块的一个不错的命令— XML,DOM,CSS,XMLHttpRequest和JavaScript,将它们紧密结合在一起,您便拥有了开始构建动态且可访问的AJAX站点所需的一切。
第2章基本XMLHttpRequest
我迫不及待想分享这个新奇观,所有人都会看到它的光芒,让他们所有人都做自己的音乐,牧师们在当晚赞美我的名字。
-匆忙,发现
XMLHttpRequest
赋予AJAX真正的力量:能够从浏览器发出异步HTTP请求并以小块形式提取内容的能力。
Web开发人员长期以来一直在使用技巧和黑客来实现此目标,同时又遇到了令人讨厌的局限性:看不见的iframe黑客迫使我们在父文档和iframe
的文档之间来回传递数据,甚至是“远程脚本” ”方法仅限于对包含JavaScript的页面进行GET请求。
使用XMLHttpRequest的现代AJAX技术在这些繁琐的方法上提供了巨大的改进,使您的应用程序既可以进行GET请求又可以进行POST请求,而无需完全重新加载页面。
在本章中,我们将直接进入并构建一个简单的AJAX Web应用程序—一个简单的站点监视应用程序,该应用程序将Web服务器上的页面ping至定时时间表。 但是在开始进行异步HTTP请求以轮询服务器之前,我们需要通过处理所有小的浏览器不兼容问题(例如在一个单独的内部实例化XMLHttpRequest对象的不同方式)来简化XMLHttpRequest类的使用。 ,可重用的代码库。
一个简单的AJAX库
简化XMLHttpRequest
类使用的一种方法是使用现有的代码库。 由于AJAX开发的日益普及,实际上有几十个可用的库,工具包和框架使XMLHttpRequest
易于使用。
但是,由于用于创建XMLHttpRequest
类的实例的代码非常简单,并且使用它的API易于理解,因此我们将编写一个非常简单的JavaScript库,该库负责处理我们需要的基本内容。
逐步创建自己的库的过程将确保您知道XMLHttpRequest
类的工作方式,并在决定使用它们时将帮助您从其他工具箱或库中获取更多信息。
开始我们的Ajax
课程
我们将从创建一个称为Ajax
的基本类开始,在其中将包装XMLHttpRequest
类的功能。
我从未用JavaScript完成过面向对象的编程-帮助!
在本节中,我们将开始在JavaScript中创建类和对象。 如果您以前从未做过,请不用担心-只要您了解面向对象编程的基础知识,它就非常简单。
在JavaScript中,我们不会像使用Java,C ++或.NET语言之一那样使用复杂的语法声明类。 我们只需编写一个构造函数来创建该类的实例。 我们需要做的是:
- 提供一个构造函数-该函数的名称就是您的类的名称
- 使用关键字this将属性添加到正在构造的对象中,后跟句点和属性名称
- 使用JavaScript的特殊函数构造函数语法,以与添加属性相同的方式向对象添加方法
这是创建一个名为HelloWorld
的简单类的代码:
function HelloWorld() {
this.message = 'Hello, world!';
this.sayMessage = function() {
window.alert(this.message);
};
}
JavaScript的面向对象编程的框架非常轻巧,但是一旦掌握了它,其功能就会出奇地好。 JavaScript中没有更高级的面向对象功能,例如继承和多态性,但是在AJAX应用程序的客户端很少需要这些功能。 这些功能对其有用的复杂业务逻辑应始终在Web服务器上,并使用XMLHttpRequest
类进行访问。
在此示例中,我们创建了一个名为HelloWorld
的类,该类具有一个属性( message
)和一个方法( sayMessage
)。 要使用此类,我们只需调用构造函数,如下所示:
var hw = new HelloWorld();
hw.sayMessage();
hw.message = 'Goodbye';
hw.sayMessage();
在这里,我们创建一个HelloWorld
实例(称为hw
),然后使用该对象显示两条消息。 第一次调用sayMessage
,默认为“ Hello,world!”。 显示信息。 然后,将对象的message
属性更改为“ Goodbye”后,我们说sayMessage
并显示“ Goodbye”。
暂时不要担心这是否太有意义。 随着我们逐步构建Ajax
类,它将变得更加清晰。
这是我们的Ajax
类的构造函数的开始:
Example 2.1. ajax.js (excerpt)
function Ajax() {
this.req = null;
this.url = null;
this.method = 'GET';
this.async = true;
this.status = null;
this.statusText = '';
this.postData = null;
this.readyState = null;
this.responseText = null;
this.responseXML = null;
this.handleResp = null;
this.responseFormat = 'text', // 'text', 'xml', or 'object'
this.mimeType = null;
}
这段代码只是定义了我们在Ajax
类中需要的属性,以便与XMLHttpRequest
对象一起使用。 现在,让我们向对象添加一些方法。 我们需要一些函数来设置XMLHttpRequest
对象,并告诉它如何向我们发出请求。
创建一个XMLHttpRequest
对象
首先,我们将添加一个init
方法,该方法将为我们创建一个XMLHttpRequest
对象。 不幸的是, XMLHttpRequest
在Firefox中的实现略有不同(在本书中,每当我解释Firefox如何工作时,我指的是所有基于Mozilla的浏览器,包括Firefox,Mozilla,Camino和SeaMonkey),Safari和Opera它是Internet Explorer的原始实现方式(有趣的是,Internet Explorer 7现在支持与Firefox相同的界面,从而有望在将来简化AJAX开发),因此,如果出现以下情况,您将不得不尝试以多种不同方式实例化对象您没有针对特定的浏览器。 Firefox和Safari使用名为XMLHttpRequest
的类创建XMLHttpRequest
对象,而Internet Explorer 6及更早版本使用称为ActiveXObject
的特殊类,该类内置于Microsoft的脚本引擎中。 尽管这些类具有不同的构造函数,但它们的行为方式相同。
跨浏览器代码
幸运的是,大多数现代浏览器(Internet Explorer 6,Firefox 1.0,Safari 1.2和Opera 8或任何这些浏览器的更高版本)总体上都很好地遵守了Web标准,因此您不必做很多特定于浏览器的事情分支您的AJAX代码。
这通常使基于浏览器的AJAX应用程序比台式机应用程序更快地开发和部署跨平台。 随着AJAX应用程序可用功能的增强,从用户界面的角度来看,桌面应用程序提供的优势越来越少。
init
方法如下所示:
Example 2.2. ajax.js (excerpt)
this.init = function() {
if (!this.req) {
try {
// Try to create object for Firefox, Safari, IE7, etc.
this.req = new XMLHttpRequest();
}
catch (e) {
try {
// Try to create object for later versions of IE.
this.req = new ActiveXObject('MSXML2.XMLHTTP');
}
catch (e) {
try {
// Try to create object for early versions of IE.
this.req = new ActiveXObject('Microsoft.XMLHTTP');
}
catch (e) {
// Could not create an XMLHttpRequest object.
return false;
}
}
}
}
return this.req;
};
init
方法通过创建XMLHttpRequest
对象的所有可能方式,直到成功创建一个对象。 然后将该对象返回到调用函数。
优雅降级
保持与旧版浏览器的兼容性(用“旧版”表示,比我在前一个注释中提到的“现代浏览器”更旧的版本)需要大量额外的代码工作,因此定义应用程序应支持的浏览器至关重要。
如果您知道您的应用程序将通过不支持XMLHtmlRequest
类的较旧的浏览器(例如,Internet Explorer 4和更早版本,Netscape 4和更早版本)收到大量流量,则需要完全将其保留,或者编写您的代码,它会优雅地降级。 这就意味着,您必须编码以确保这些浏览器的用户收到功能上等效的内容,尽管可能采用交互性较小或易于使用的格式,而不是仅仅让其功能在功能不强大的浏览器中消失。
您的网站也可能吸引那些禁用JavaScript的用户。 如果您想迎合这些用户,则默认情况下,您应该提供一个替代的老式界面,然后您可以使用JavaScript为现代浏览器即时进行修改。
发送请求
现在,我们有了一个创建XMLHttpRequest
的方法。 因此,让我们编写一个使用它来发出请求的函数。 我们这样启动doReq方法:
Example 2.3. ajax.js (excerpt)
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
};
doReq
第一部分调用init
来创建XMLHttpRequest
类的实例,并在失败时显示一个快速警报。
设置请求
接下来,我们的代码在this.req
(我们的XMLHttpRequest
类的新实例)上调用open
方法,以开始设置HTTP请求:
Example 2.4. ajax.js (excerpt)
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
this.req.open(this.method, this.url, this.async);
};
open
方法采用三个参数:
1.方法 -此参数标识我们将使用的HTTP请求方法的类型。 最常用的方法是GET和POST。
方法区分大小写
根据HTTP规范(RFC 2616),这些请求方法的名称区分大小写。 并且由于规范中描述的方法被定义为全部大写,因此应始终确保以所有大写字母键入该方法。
2. URL –此参数标识正在请求的页面(如果方法是POST,则发布到该页面)。
跨域
正常的浏览器安全设置将不允许您将HTTP请求发送到另一个域。 例如,除非用户允许,否则从ajax.net服务的页面将无法将请求发送到remotescripting.com。
3.异步标志 –如果将此参数设置为true
,则在等待对请求的响应时,您的JavaScript将继续正常执行。 随着请求状态的更改,将触发事件,以便您可以处理请求状态的更改。
如果将参数设置为false
,则JavaScript执行将停止,直到响应从服务器返回为止。 这种方法的优点是比使用回调函数简单一些,因为您可以在代码中发送请求后立即开始处理响应,但是最大的缺点是代码在发送和处理请求时暂停在服务器上,并收到响应。 由于与服务器异步通信的能力是AJAX应用程序的重点,因此应将其设置为true
。
在我们的Ajax
类中,将method和async属性初始化为合理的默认值(GET和true),但是,当然,您始终必须设置目标URL。
设置onreadystatechange
事件处理程序
在服务器上处理HTTP请求时,更改readyState属性可指示其进度。 此属性是一个整数,代表以下状态之一,从请求开始到结束依次列出:
-
0
:未初始化–尚未调用open
。 -
1
:正在加载–send
尚未被调用。 -
2
:已加载–已调用send
,但响应尚不可用。 -
3
:交互式–正在下载响应,并且responseText属性保存部分数据。 -
4
:已完成–响应已加载且请求已完成。
XMLHttpRequest
对象通过触发readystatechange
事件来告诉您状态的每一个变化。 在此事件的处理程序中,检查请求的readyState
,并在请求完成时(即,当readyState
更改为4
),可以处理服务器的响应。
我们的Ajax
代码的基本轮廓如下所示:
Example 2.5. ajax.js (excerpt)
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
this.req.open(this.method, this.url, this.async);
var self = this; // Fix loss-of-scope in inner function
this.req.onreadystatechange = function() {
if (self.req.readyState == 4) {
// Do stuff to handle response
}
};
};
我们将稍后讨论如何“做一些事情来处理响应”。 现在,请记住,您需要在发送请求之前设置此事件处理程序。
发送请求
使用XMLHttpRequest
类的send
方法来启动HTTP请求,如下所示:
Example 2.6. ajax.js (excerpt)
this.doReq = function() {
if (!this.init()) {
alert('Could not create XMLHttpRequest object.');
return;
}
this.req.open(this.method, this.url, this.async);
var self = this; // Fix loss-of-scope in inner function
this.req.onreadystatechange = function() {
if (self.req.readyState == 4) {
// Do stuff to handle response
}
};
this.req.send(this.postData);
};
send方法采用一个参数,该参数用于POST
数据。 当请求是不将任何数据传递到服务器的简单GET
(如当前请求),我们将此参数设置为null。
范围的损失和this
您可能已经注意到onreadystatechange
包含一个看起来很奇怪的变量分配:
Example 2.7. ajax.js (excerpt)
var self = this; // Fix loss-of-scope in inner function
这个新变量self
是解决“范围损失”问题的解决方案,JavaScript开发人员经常使用异步事件处理程序来解决此问题。 异步事件处理程序通常与XMLHttpRequest
以及setTimeout
或setInterval
类的功能结合使用。
this
关键字在面向对象的JavaScript代码中用作表示“当前对象”的简写形式。 这是一个简单的示例—名为ScopeTest
的类:
function ScopeTest() {
this.message = 'Greetings from ScopeTest!';
this.doTest = function() {
alert(this.message);
};
}
var test = new ScopeTest();
test.doTest();
该代码将创建ScopeTest
类的实例,然后调用该对象的doTest
方法,该方法将显示消息“来自ScopeTest的问候!”。 简单吧?
Now, let's add some simple XMLHttpRequest
code to our ScopeTest
class. We'll send a simple GET
request for your web server's home page, and, when a response is received, we'll display the content of both this.message
and self.message
.
function ScopeTest() {
this.message = 'Greetings from ScopeTest!';
this.doTest = function() {
// This will only work in Firefox, Opera and Safari.
this.req = new XMLHttpRequest();
this.req.open('GET', '/index.html', true);
var self = this;
this.req.onreadystatechange = function() {
if (self.req.readyState == 4) {
var result = 'self.message is ' + self.message;
result += 'n';
result += 'this.message is ' + this.message;
alert(result);
}
}
this.req.send(null);
};
}
var test = new ScopeTest();
test.doTest();
So, what message is displayed? The answer is revealed in Figure 2.1.
We can see that self.message
is the greeting message that we're expecting, but what's happened to this.message
?
Using the keyword this
is a convenient way to refer to "the object that's executing this code." But this has one small problem — its meaning changes when it's called from outside the object. This is the result of something called execution context. All of the code inside the object runs in the same execution context, but code that's run from other objects — such as event handlers — runs in the calling object's execution context. What this means is that, when you're writing object-oriented JavaScript, you won't be able to use the this
keyword to refer to the object in code for event handlers (like onreadystatechange
above). This problem is called loss of scope.
If this concept isn't 100% clear to you yet, don't worry too much about it. We'll see an actual demonstration of this problem in the next chapter. In the meantime, just kind of keep in mind that if you see the variable self in code examples, it's been included to deal with a loss-of-scope problem.
Figure 2.1. Message displayed by ScopeTest class
Processing the Response
Now we're ready to write some code to handle the server's response to our HTTP request. Remember the "do stuff to handle response" comment that we left in the onreadystatechange
event handler? We'll, it's time we wrote some code to do that stuff! The function needs to do three things:
- Figure out if the response is an error or not.
- Prepare the response in the desired format.
- Pass the response to the desired handler function.
Include the code below in the inner function of our Ajax
class:
Example 2.8. ajax.js (excerpt)
this.req.onreadystatechange = function() {
var resp = null;
if (self.req.readyState == 4) {
switch (self.responseFormat) {
case 'text':
resp = self.req.responseText;
break;
case 'xml':
resp = self.req.responseXML;
break;
case 'object':
resp = req;
break;
}
if (self.req.status >= 200 && self.req.status <= 299) {
self.handleResp(resp);
}
else {
self.handleErr(resp);
}
}
};
When the response completes, a code indicating whether or not the request succeeded is returned in the status property of our XMLHttpRequest
object. The status property contains the HTTP status code of the completed request. This could be code 404 if the requested page was missing, 500 if an error occurred in the server-side script, 200 if the request was successful, and so on. A full list of these codes is provided in the HTTP Specification (RFC 2616) .
No Good with Numbers?
If you have trouble remembering the codes, don't worry: you can use the statusText property, which contains a short message that tells you a bit more detail about the error (eg, "Not Found," "Internal Server Error," "OK").
Our Ajax
class will be able to provide the response from the server in three different formats: as a normal JavaScript string, as an XML document object accessible via the W3C XML DOM, and as the actual XMLHttpRequest
object that was used to make the request. These are controlled by the Ajax
class's responseFormat
property, which can be set to text
, xml
or object
.
The content of the response can be accessed via two properties of our XMLHttpRequest
object:
-
responseText
– This property contains the response from the server as a normal string. In the case of an error, it will contain the web server's error page HTML. As long as a response is returned (that is,readyState
becomes 4), this property will contain data, though it may not be what you expect. -
responseXML
– This property contains an XML document object. If the response is not XML, this property will be empty.
Our Ajax
class initializes its responseFormat
property to text, so by default, your response handler will be passed the content from the server as a JavaScript string. If you're working with XML content, you can change the responseFormat
property to xml
, which will pull out the XML document object instead.
There's one more option you can use if you want to get really fancy: you can return the actual XMLHttpRequest
object itself to your handler function. This gives you direct access to things like the status and statusText
properties, and might be useful in cases in which you want to treat particular classes of errors differently — for example, completing extra logging in the case of 404 errors.
Setting the Correct Content-Type
Implementations of XMLHttpRequest
in all major browsers require the HTTP response's Content-Type
to be set properly in order for the response to be handled as XML. Well-formed XML, returned with a content type of text/xml
(or application/xml
, or even application/xhtml+xml
), will properly populate the responseXML
property of an XMLHttpRequest
object; non-XML content types will result in values of null
or undefined
for that property.
However, Firefox, Safari, and Internet Explorer 7 provide a way around XMLHttpRequest
's pickiness over XML documents: the overrideMimeType
method of the XMLHttpRequest
class. Our simple Ajax
class hooks into this with the setMimeType
method:
Example 2.9. ajax.js (excerpt)
this.setMimeType = function(mimeType) {
this.mimeType = mimeType;
};
This method sets the mimeType
property.
Then, in our doReq
method, we simply call overrideMimeType
inside a try ... catch
block, like so:
Example 2.10. ajax.js (excerpt)
req.open(this.method, this.url, this.async);
if (this.mimeType) {
try {
req.overrideMimeType(this.mimeType);
}
catch (e) {
// couldn't override MIME type -- IE6 or Opera?
}
}
var self = this; // Fix loss-of-scope in inner function
Being able to override Content-Type
headers from uncooperative servers can be very important in environments in which you don't have control over both the front and back ends of your web application. This is especially true since many of today's apps access services and content from a lot of disparate domains or sources. However, as this technique won't work in Internet Explorer 6 or Opera 8, you may not find it suitable for use in your applications today.
Response Handler
According to the HTTP 1.1 specification, any response that has a code between 200 and 299 inclusive is a successful response.
The onreadystatechange
event handler we've defined looks at the status property to get the status of the response. If the code is within the correct range for a successful response, the onreadystatechange
event handler passes the response to the response handler method (which is set by the handleResp
property).
The response handler will need to know what the response was, of course, so we'll pass it the response as a parameter. We'll see this process in action later, when we talk about the doGet method.
Since the handler method is user-defined, the code also does a cursory check to make sure the method has been set properly before it tries to execute the method.
Error Handler
If the status property indicates that there's an error with the request (ie, it's outside the 200 to 299 code range), the server's response is passed to the error handler in the handleErr property. Our Ajax class already defines a reasonable default for the error handler, so we don't have to make sure it's defined before we call it.
The handleErr
property points to a function that looks like this:
Example 2.11. ajax.js (excerpt)
this.handleErr = function() {
var errorWin;
try {
errorWin = window.open('', 'errorWin');
errorWin.document.body.innerHTML = this.responseText;
}
catch (e) {
alert('An error occurred, but the error message cannot be '
+ 'displayed. This is probably because of your browser's '
+ 'pop-up blocker.n'
+ 'Please allow pop-ups from this web site if you want to '
+ 'see the full error messages.n'
+ 'n'
+ 'Status Code: ' + this.req.status + 'n'
+ 'Status Description: ' + this.req.statusText);
}
};
This method checks to make sure that pop-ups are not blocked, then tries to display the full text of the server's error page content in a new browser window. This code uses a try ... catch
block, so if users have blocked pop-ups, we can show them a cut-down version of the error message and tell them how to access a more detailed error message.
This is a decent default for starters, although you may want to show less information to the end-user — it all depends on your level of paranoia. If you want to use your own custom error handler, you can use setHandlerErr
like so:
Example 2.12. ajax.js (excerpt)
this.setHandlerErr = function(funcRef) {
this.handleErr = funcRef;
}
Or, the One True Handler
It's possible that you might want to use a single function to handle both successful responses and errors. setHandlerBoth
, a convenience method in our Ajax
class, sets this up easily for us:
Example 2.13. ajax.js (excerpt)
this.setHandlerBoth = function(funcRef) {
this.handleResp = funcRef;
this.handleErr = funcRef;
};
Any function that's passed as a parameter to setHandlerBoth
will handle both successful responses and errors.
This setup might be useful to a user who sets your class's responseFormat
property to object, which would cause the XMLHttpRequest
object that's used to make the request — rather than just the value of the responseText
or responseXML
properties — to be passed to the response handler.
Aborting the Request
Sometimes, as you'll know from your own experience, a web page will take a very long time to load. Your web browser has a Stop button, but what about your Ajax
class? This is where the abort
method comes into play:
Example 2.14. ajax.js (excerpt)
this.abort = function() {
if (this.req) {
this.req.onreadystatechange = function() { };
this.req.abort();
this.req = null;
}
};
This method changes the onreadystate
event handler to an empty function, calls the abort
method on your instance of the XMLHttpRequest
class, then destroys the instance you've created. That way, any properties that have been set exclusively for the request that's being aborted are reset. Next time a request is made, the init
method will be called and those properties will be reinitialized.
So, why do we need to change the onreadystate
event handler? Many implementations of XMLHttpRequest
will fire the onreadystate event once abort is called, to indicate that the request's state has been changed. What's worse is that those events come complete with a readyState
of 4, which indicates that everything completed as expected (which is partly true, if you think about it: as soon as we call abort, everything should come to a stop and our instance of XMLHttpRequest
should be ready to send another request, should we so desire). Obviously, we don't want our response handler to be invoked when we abort a request, so we remove the existing handler just before we call abort
.
Wrapping it Up
Given the code we have so far, the Ajax class needs just two things in order to make a request:
- a target URL
- a handler function for the response
Let's provide a method called doGet
to set both of these properties, and kick off the request:
Example 2.15. ajax.js (excerpt)
this.doGet = function(url, hand, format) {
this.url = url;
this.handleResp = hand;
this.responseFormat = format || 'text';
this.doReq();
};
You'll notice that, along with the two expected parameters, url
and hand
, the function has a third parameter: format
. This is an optional parameter that allows us to change the format of the server response that's passed to the handler function.
If we don't pass in a value for format, the responseFormat
property of the Ajax
class will default to a value of text, which means your handler will be passed the value of the responseText
property. You could, instead, pass xml
or object
as the format, which would change the parameter that's being passed to the response handler to an XML DOM or XMLHttpRequest
object.
Example: a Simple Test Page
It's finally time to put everything we've learned together! Let's create an instance of this Ajax
class, and use it to send a request and handle a response.
Now that our class's code is in a file called ajax.js
, any web pages in which we want to use our Ajax
class will need to include the Ajax code with a <script type="text/javascript" src="ajax.js">
tag. Once our page has access to the Ajax code, we can create an Ajax
object.
Example 2.16. ajaxtest.html (excerpt)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1" />
<title>A Simple AJAX Test</title>
<script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript">
var ajax = new Ajax();
</script>
</head>
<body>
</body>
</html>
This script gives us a shiny, new instance of the Ajax
class. Now, let's make it do something useful.
To make the most basic request with our Ajax
class, we could do something like this:
Example 2.17. ajaxtest.html (excerpt)
<script type="text/javascript">
var hand = function(str) {
alert(str);
}
var ajax = new Ajax();
ajax.doGet('/fakeserver.php', hand);
</script>
This creates an instance of our Ajax
class that will make a simple GET
request to a page called fakeserver.php
, and pass the result back as text to the hand function. If fakeserver.php
returned an XML document that you wanted to use, you could do so like this:
Example 2.18. ajaxtest.html (excerpt)
<script type="text/javascript">
var hand = function(str) {
// Do XML stuff here
}
var ajax = new Ajax();
ajax.doGet('/fakeserver.php', hand);
</script>
You would want to make absolutely sure in this case that somepage.php was really serving valid XML and that its Content-Type
HTTP response header was set to text/xml
(or something else that was appropriate).
Creating the Page
Now that we have created the Ajax
object, and set up a simple handler function for the request, it's time to put our code into action.
The Fake Server Page
In the code above, you can see that the target URL for the request is set to a page called fakeserver.php
. To use this demonstration code, you'll need to serve both ajaxtest.html
and fakeserver.php
from the same PHP-enabled web server. You can do this from an IIS web server with some simple ASP, too. The fake server page is a super-simple page that simulates the varying response time of a web server using the PHP code below:
Example 2.19. fakeserver.php
<?php
header('Content-Type: text/plain');
sleep(rand(3, 12));
print 'ok';
?>
That's all this little scrap of code does: it waits somewhere between three and 12 seconds, then prints ok.
The fakeserver.php
code sets the Content-Type
header of the response to text/plain
. Depending on the content of the page you pass back, you might choose another Content-Type
for your response. For example, if you're passing an XML document back to the caller, you would naturally want to use text/xml
.
This works just as well in ASP, although some features (such as sleep) are not as easily available, as the code below illustrates:
Example 2.20. fakeserver.asp
<%
Response.ContentType = "text/plain"
' There is no equivalent to sleep in ASP.
Response.Write "ok"
%>
Throughout this book, all of our server-side examples will be written in PHP, although they could just as easily be written in ASP, ASP.NET, Java, Perl, or just about any language that can serve content through a web server.
Use the setMimeType
Method
Imagine that you have a response that you know contains a valid XML document that you want to parse as XML, but the server insists on serving it to you as text/plain. You can force that response to be parsed as XML in Firefox and Safari by adding an extra call to setMimeType
, like so:
var ajax = new Ajax();
ajax.setMimeType('text/xml');
ajax.doGet('/fakeserver.php', hand, 'xml');
Naturally, you should use this approach only when you're certain that the response from the server will be valid XML, and you can be sure that the browser is Firefox or Safari.
Hitting the Page
Now comes the moment of truth! Hit your local web server, load up ajaxtest.html
, and see what you get. If everything is working properly, there will be a few moments' delay, and then you'll see a standard JavaScript alert like the one in Figure 2.2 that says simply ok.
Figure 2.2. Confirmation that your Ajax
class is working as expected
Now that all is well and our Ajax
class is functioning properly, it's time to move to the next step.
Example: a Simple AJAX App
Okay, so using the awesome power of AJAX to spawn a tiny little JavaScript alert box that reads "ok"
is probably not exactly what you had in mind when you bought this book. Let's implement some changes to our example code that will make this XMLHttpRequest stuff a little more useful. At the same time, we'll create that simple monitoring application I mentioned at the start of this chapter. The app will ping a web site and report the time it takes to get a response back.
Laying the Foundations
We'll start off with a simple HTML document that links to two JavaScript files: ajax.js
, which contains our library, and appmonitor1.js
, which will contain the code for our application.
Example 2.21. appmonitor1.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1" />
<title>App Monitor</title>
<script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript" src="appmonitor1.js"></script>
</head>
<body>
<div id="pollDiv"></div>
</body>
</html>
You'll notice that there's virtually no content in the body of the page — there's just a single div
element. This is fairly typical of web apps that rely on AJAX functions. Often, much of the content of AJAX apps is created by JavaScript dynamically, so we usually see a lot less markup in the body of the page source than we would in a non-AJAX web application for which all the content was generated by the server. However, where AJAX is not an absolutely essential part of the application, a plain HTML version of the application should be provided.
We'll begin our appmonitor1.js
file with some simple content that makes use of our Ajax
class:
Example 2.22. appmonitor1.js (excerpt)
var start = 0;
var ajax = new Ajax();
var doPoll = function() {
start = new Date();
start = start.getTime();
ajax.doGet('/fakeserver.php?start=' + start, showPoll);
}
window.onload = doPoll;
We'll use the start variable to record the time at which each request starts — this figure will be used to calculate how long each request takes. We make start a global variable so that we don't have to gum up the works of our Ajax
class with extra code for timing requests — we can set the value of start immediately before and after our calls to the Ajax
object.
The ajax
variable simply holds an instance of our Ajax
class.
The doPoll
function actually makes the HTTP requests using the Ajax
class. You should recognize the call to the doGet
method from our original test page.
Notice that we've added to the target URL a query string that has the start value as a parameter. We're not actually going to use this value on the server; we're just using it as a random value to deal with Internet Explorer's overzealous caching. IE caches all GET
requests made with XMLHttpRequest
, and one way of disabling that "feature" is to append a random value into a query string. The milliseconds value in start can double as that random value. An alternative to this approach is to use the setRequestHeader
method of the XMLHttpRequest
class to set the If-Modified-Since
header on the request.
Finally, we kick everything off by attaching doPoll
to the window.onload
event.
Handling the Result with showPoll
The second parameter we pass to doGet
tells the Ajax
class to pass responses to the function showPoll
. Here's the code for that function:
Example 2.23. appmonitor1.js (excerpt)
var showPoll = function(str) {
var pollResult = '';
var diff = 0;
var end = new Date();
if (str == 'ok') {
end = end.getTime();
diff = (end - start) / 1000;
pollResult = 'Server response time: ' + diff + ' seconds';
}
else {
pollResult = 'Request failed.';
}
printResult(pollResult);
var pollHand = setTimeout(doPoll, 15000);
}
This is all pretty simple: the function expects a single parameter, which should be the string ok
returned from fakeserver.php
if everything goes as expected. If the response is correct, the code does the quick calculations needed to figure out how long the response took, and creates a message that contains the result. It passes that message to pollResult for display.
In this very simple implementation, anything other than the expected response results in a fairly terse and unhelpful message: Request failed. We'll make our handling of error conditions more robust when we upgrade this app in the next chapter.
Once pollResult
is set, it's passed to the printResult
function:
Example 2.24. appmonitor1.js (excerpt)
function printResult(str) {
var pollDiv = document.getElementById('pollDiv');
if (pollDiv.firstChild) {
pollDiv.removeChild(pollDiv.firstChild);
}
pollDiv.appendChild(document.createTextNode(str));
}
The printResult
function displays the message that was sent from showPoll
inside the lone div
in the page.
Note the test in the code above, which is used to see whether our div
has any child nodes. This checks for the existence of any text nodes, which could include text that we added to this div
in previous iterations, or the text that was contained inside the div
in the page markup, and then removes them. If you don't remove existing text nodes, the code will simply append the new result to the page as a new text node: you'll display a long string of text to which more text is continually being appended.
Why Not Use innerHTML
?
You could simply update the innerHTML
property of the div
, like so:
document.getElementById('pollDiv').innerHTML = str;
The innerHTML
property is not a web standard, but all the major browsers support it. And, as you can see from the fact that it's a single line of code (as compared with the four lines needed for DOM methods), sometimes it's just easier to use than the DOM methods. Neither way of displaying content on your page is inherently better.
In some cases, you may end up choosing a method based on the differences in rendering speeds of these two approaches ( innerHTML
can be faster than DOM methods). In other cases, you may base your decision on the clarity of the code, or even on personal taste.
Starting the Process Over Again
Finally, showPoll
starts the entire process over by scheduling a call to the original doPoll
function in 15 seconds time using setTimeout
, as shown below:
Example 2.25. appmonitor1.js (excerpt)
var pollHand = setTimeout(doPoll, 15000);
The fact that the code continuously invokes the doPoll
function means that once the page loads, the HTTP requests polling the fakeserver.php
page will continue to do so until that page is closed. The pollHand
variable is the interval ID that allows you to keep track of the pending operation, and cancel it using clearTimeout
.
The first parameter of the setTimeout
call, doPoll
, is a pointer to the main function of the application; the second represents the length of time, in seconds, that must elapse between requests.
Full Example Code
Here's all the code from our first trial run with this simple monitoring application.
Example 2.26. appmonitor1.js
var start = 0;
var ajax = new Ajax();
var doPoll = function() {
start = new Date();
start = start.getTime();
ajax.doGet('/fakeserver.php?start=' + start, showPoll);
}
window.onload = doPoll;
var showPoll = function(str) {
var pollResult = '';
var diff = 0;
var end = new Date();
if (str == 'ok') {
end = end.getTime();
diff = (end - start)/1000;
pollResult = 'Server response time: ' + diff + ' seconds';
}
else {
pollResult = 'Request failed.';
}
printResult(pollResult);
var pollHand = setTimeout(doPoll, 15000);
}
function printResult(str) {
var pollDiv = document.getElementById('pollDiv');
if (pollDiv.firstChild) {
pollDiv.removeChild(pollDiv.firstChild);
}
pollDiv.appendChild(document.createTextNode(str));
}
In a bid to follow good software engineering principles, I've separated the JavaScript code from the markup, and put them in two different files.
I'll be following a similar approach with all the example code for this book, separating each example's markup, JavaScript code, and CSS into separate files. This little monitoring app is so basic that it has no CSS file. We'll be adding a few styles to make it look nicer in the next chapter.
Running the App
Try loading the page in your browser. Drop it into your web server's root directory, and open the page in your browser.
If the fakeserver.php
page is responding properly, you'll see something like the display shown in Figure 2.3.
Figure 2.3. Running the simple monitoring application
进一步阅读
Here are some online resources that will help you learn more about the techniques and concepts in this chapter.
JavaScript's Object Model
Check out these two chapters on objects from the Client-Side JavaScript Guide for version 1.3 of JavaScript, hosted by Sun Microsystems. The first chapter explains all the basic concepts you need to understand how to work with objects in JavaScript. The second goes into more depth about JavaScript's prototype-based inheritance model, allowing you to leverage more of the power of object-oriented coding with JavaScript.
This is a brief introduction to creating private instance variables with JavaScript objects. It will help you get a deeper understanding of JavaScript's prototype-based inheritance scheme.
XMLHttpRequest
Here's a good reference page from the Apple Developer Connection. It gives a nice overview of the XMLHttpRequest class, and a reference table of its methods and properties.
This article , originally posted in 2002, continues to be updated with new information. It includes information on making HEAD requests (instead of just GET or POST), as well as JavaScript Object Notation (JSON), and SOAP.
This is XULPlanet's exhaustive reference on the XMLHttpRequest
implementation in Firefox.
Here's another nice overview , which also shows some of the lesser-used methods of the XMLHttpRequest
object, such as overrideMimeType
, setRequestHeader
, and getResponseHeader
. Again, this reference is focused on implementation in Firefox.
This is Microsoft's documentation on MSDN of its implementation of XMLHttpRequest
.
摘要
XMLHttpRequest
is at the heart of AJAX. It gives scripts within the browser the ability to make their own requests and get content from the server. The simple AJAX library we built in this chapter provided a solid understanding of howXMLHttpRequest
works, and that understanding will help you when things go wrong with your AJAX code (whether you're using a library you've built yourself, or one of the many pre-built toolkits and libraries listed in Appendix A, AJAX Toolkits). The sample app we built in this chapter gave us a chance to dip our toes into the AJAX pool -- now it's time to dive in and learn to swim.Chapter 3. The "A" in AJAX
It's flying over our heads in a million pieces.-- Willy Wonka, Willy Wonka & the Chocolate Factory
The "A" in AJAX stands for "asynchronous," and while it's not nearly as cool as the letter "X," that "A" is what makes AJAX development so powerful. As we discussed in Chapter 1, AJAX: the Overview, AJAX's ability to update sections of an interface asynchronously has given developers a much greater level of control over the interactivity of the apps we build, and a degree of power that's driving web apps into what was previously the domain of desktop applications alone.
Back in the early days of web applications, users interacted with data by filling out forms and submitting them. Then they'd wait a bit, watching their browser's "page loading" animation until a whole new page came back from the server. Each data transaction between the browser and server was large and obvious, which made it easy for users to figure out what was going on, and what state their data was in.
As AJAX-style development becomes more popular, users can expect more interactive, "snappy" user interfaces. This is a good thing for users, but presents new challenges for the developers working to deliver this increased functionality. In an AJAX application, users alter data in an ad hoc fashion, so it's easy for both the user and the application to become confused about the state of that data.
The solution to both these issues is to display the application's status, which keeps users informed about what the application is doing. This makes the application seem very responsive, and gives users important guidance about what's happening to their data. This critical part of AJAX web application development is what separates the good AJAX apps from the bad.
Planned Application Enhancements
To create a snappy user interface that keeps users well-informed of the application's status, we'll take the monitoring script we developed in the previous chapter, and add some important functionality to it. Here's what we're going to add:
- a way for the system administrator to configure the interval between polls and the timeout threshold
- an easy way to start and stop the monitoring process
- a bar graph of response times for previous requests; the number of entries in the history list will be user-configurable
- user notification when the application is in the process of making a request
- graceful handling of request timeouts
Figure 3.1 shows what the running application will look like once we're done with all the enhancements.
The code for this application is broken up into three files: the markup in appmonitor2.html
, the JavaScript code in appmonitor2.js
, and the styles in appmonitor2.css
. To start with, we'll link all the required files in to appmonitor2.html
:
Example 3.1. appmonitor2.html (excerpt)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1" />
<title>App Monitor</title>
<script type="text/javascript" src="ajax.js"></script>
<script type="text/javascript" src="appmonitor2.js"></script>
<link rel="stylesheet" href="appmonitor2.css"
type="text/css" />
</head>
<body>
</body>
</html>
Figure 3.1. The running application
Organizing the Code
All this new functionality will add a lot more complexity to our app, so this is a good time to establish some kind of organization within our code (a much better option than leaving everything in the global scope). After all, we're building a fully functional AJAX application, so we'll want to have it organized properly.
We'll use object-oriented design principles to organize our app. And we'll start, of course, with the creation of a base class for our application — the Monitor
class.
Typically, we'd create a class in JavaScript like this:
function Monitor() {
this.firstProperty = 'foo';
this.secondProperty = true;
this.firstMethod = function() {
// Do some stuff here
};
}
This is a nice, normal constructor function, and we could easily use it to create a Monitor
class (or a bunch of them if we wanted to).
Loss of Scope with setTimeout
Unfortunately, things will not be quite so easy in the case of our application. We're going to use a lot of calls to setTimeout
(as well as setInterval
) in our app, so the normal method of creating JavaScript classes may prove troublesome for our Monitor
class.
The setTimeout
function is really handy for delaying the execution of a piece of code, but it has a serious drawback: it runs that code in an execution context that's different from that of the object. (We talked a little bit about this problem, called loss of scope, in the last chapter.)
This is a problem because the object keyword this
has a new meaning in the new execution context. So, when you use it within your class, it suffers from a sudden bout of amnesia — it has no idea what it is!
This may be a bit difficult to understand; let's walk through a quick demonstration so you can actually see this annoyance in action. You might remember the ScopeTest
class we looked at in the last chapter. To start with, it was a simple class with one property and one method:
function ScopeTest() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
alert(this.message);
};
}
var test = new ScopeTest();
test.doTest();
The result of this code is the predictable JavaScript alert box with the text "Greetings from ScopeTest!"
Let's change the doTest method so that it uses setTimeout
to display the message in one second's time.
function ScopeTest() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
var onTimeout = function() {
alert(this.message);
};
setTimeout(onTimeout, 1000);
};
}
var test = new ScopeTest();
test.doTest();
Instead of our greeting message, the alert box that results from this version of the code will read "undefined." Because we called onTimeout
with setTimeout
, onTimeout
is run within a new execution context. In that execution context, this no longer refers to an instance of ScopeTest
, so this.message
has no meaning.
The simplest way to deal with this problem of loss of scope is by making the Monitor
class a special kind of class, called a singleton.
Singletons with JavaScript
A "singleton" is called that because only a "single" instance of that class exists at any time. Making a class into a singleton is surprisingly easy:
var ScopeTest = new function() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
var onTimeout = function() {
alert(this.message);
};
setTimeout(onTimeout, 1000);
};
}
Using the keyword new before function creates a "one-shot" constructor. It creates a single instance of ScopeTest
, and it's done: you can't use it to create any more ScopeTest
objects.
To call the doTest
method of this singleton object, you must use the actual name of the class (since there's only the one instance of it):
ScopeTest.doTest();
That's all well and good, but we haven't solved our loss of scope problem. If you were to try the code now, you'd get the same "undefined" message you saw before, because this doesn't refer to an instance of ScopeTest
. However, using a singleton gives us an easy way to fix the problem. All we have to do is use the actual name of the object — instead of the keyword this
— inside onTimeout
:
var ScopeTest = new function() {
this.message = "Greetings from ScopeTest!";
this.doTest = function() {
var onTimeout = function() {
alert(ScopeTest.message);
};
setTimeout(onTimeout, 1000);
};
}
There's only one instance of ScopeTest
, and we're using its actual name instead of this
, so there's no confusion about which instance of ScopeTest
is being referred to here.
When you execute this code, you'll see the expected value of "Greetings from ScopeTest!" in the JavaScript alert box.
Now, I get tired of using the actual object name throughout my object code, and I like to use a shortcut keyword like this wherever I possibly can. So, usually I create a variable self
that I can use in place of this
, and point it to the object name at the top of each method, like so:
var onTimeout = function() {
var self = ScopeTest;
alert(self.message);
};
This looks a bit silly in a method that's as short as that, but in longer chunks of code it's nice to have a shorthand solution similar to this that you can use to refer to your object. I use self
, but you could use me
, or heyYou
, or darthVader
if you wanted to.
Creating the Monitor Object
Now that we have a plan for code organization that will fix the loss-of-scope problem from setTimeout
, it's time to create our base Monitor
class:
Example 3.2. appmonitor2.js (excerpt)
var Monitor = new function(){
this.targetURL = null;
this.pollInterval = null;
this.maxPollEntries = null;
this.timeoutThreshold = null;
this.ajax = new Ajax();
this.start = 0;
this.pollArray = [];
this.pollHand = null;
this.timeoutHand = null;
this.reqStatus = Status;
}
The first four properties, targetURL
, pollInterval
, maxPollEntries
, and timeoutThreshold
, will be initialized as part of the class's initialization. They will take on the values defined in the application's configuration, which we'll look at in the next section.
Here's a brief rundown on the other properties:
-
ajax
– The instance of our Ajax class that makes the HTTP requests to the server we're monitoring. -
start
– Used to record the time at which the last request was sent. -
pollArray
– An array that holds the server response times; the constantMAX_POLL_ENTRIES
determines the number of items held in this array. -
pollHand
,timeoutHand
– Interval IDs returned by thesetTimeout
calls for two different processes — the main polling process, and the timeout watcher, which controls a user-defined timeout period for each request. -
reqStatus
– Used for the status animation that notifies the user when a request is in progress. The code that achieved this is fairly complicated, so we'll be writing another singleton class to take care of it. ThereqStatus
property points to the single instance of that class.
Configuring and Initializing our Application
A webmaster looking at this application may think that it was quite cool, but one of the first things he or she would want is an easy way to configure the app's polling interval, or the time that elapses between requests the app makes to the site it's monitoring. It's easy to configure the polling interval using a global constant.
To make it very simple for any user of this script to set the polling interval, we'll put this section of the code in a script element within the head of appmonitor2.html
:
Example 3.3. appmonitor2.html (excerpt)
<script type="text/javascript">
// URL to monitor
var TARGET_URL = '/fakeserver.php';
// Seconds between requests
var POLL_INTERVAL = 5;
// How many entries bars to show in the bar graph
var MAX_POLL_ENTRIES = 10;
// Seconds to wait for server response
var TIMEOUT_THRESHOLD = 10;
</script>
You'll notice that these variable names are written in all-caps. This is an indication that they should act like constants — values that are set early in the code, and do not change as the code executes. Constants are a feature of many programming languages but, unfortunately, JavaScript is not one of them. (Newer versions of JavaScript allow you to set real constants with the constkeyword, but this facility isn't widely supported (even by many modern browsers).) Note that these constants relate directly to the first four properties of our class: targetURL
, pollInterval
, maxPollEntries
, and timeoutThreshold
. These properties will be initialized in our class's init
method:
Example 3.4. appmonitor2.js (excerpt)
this.init = function() {
var self = Monitor;
self.targetURL = TARGET_URL;
self.pollInterval = POLL_INTERVAL;
self.maxPollEntries = MAX_POLL_ENTRIES;
self.timeoutThreshold = TIMEOUT_THRESHOLD;
self.toggleAppStatus(true);
self.reqStatus.init();
};
As well as initializing some of the properties of our class, the init
method also calls two methods: toggleAppStatus
, which is responsible for starting and stopping the polling, and the init
method of the reqStatus
object. reqStatus is the instance of the Status
singleton class that we discussed a moment ago.
This init
method is tied to the window.onload
event for the page, like so:
Example 3.5. appmonitor2.js (excerpt)
window.onload = Monitor.init;
Setting Up the UI
The first version of this application started when the page loaded, and ran until the browser window was closed. In this version, we want to give users a button that they can use to toggle the polling process on or off. The toggleAppStatus
method handles this for us:
Example 3.6. appmonitor2.js (excerpt)
this.toggleAppStatus = function(stopped) {
var self = Monitor;
self.toggleButton(stopped);
self.toggleStatusMessage(stopped);
};
Okay, so toggleAppStatus
doesn't really do the work, but it calls the methods that do: toggleButton
, which changes Start buttons into Stop buttons and vice versa, and toggleStatusMessage
, which updates the application's status message. Let's take a closer look at each of these methods.
The toggleButton
Method
This method toggles the main application between its "Stop" and "Start" states. It uses DOM-manipulation methods to create the appropriate button dynamically, assigning it the correct text and an onclick event handler:
Example 3.7. appmonitor2.js (excerpt)
this.toggleButton = function(stopped) {
var self = Monitor;
var buttonDiv = document.getElementById('buttonArea');
var but = document.createElement('input');
but.type = 'button';
but.className = 'inputButton';
if (stopped) {
but.value = 'Start';
but.onclick = self.pollServerStart;
}
else {
but.value = 'Stop';
but.onclick = self.pollServerStop;
}
if (buttonDiv.firstChild) {
buttonDiv.removeChild(buttonDiv.firstChild);
}
buttonDiv.appendChild(but);
buttonDiv = null;
};
The only parameter to this method, stopped
, can either be true
, indicating that the polling has been stopped, or false
, indicating that polling has started.
As you can see in the code for this method, the button is created, and is set to display Start if the application is stopped, or Stop if the application is currently polling the server. It also assigns either pollServerStart
or pollServerStop
as the button's onclick event handler. These event handlers will start or stop the polling process respectively.
When this method is called from init
(via toggleAppStatus
), stopped
is set to true
so the button will display Start when the application is started.
As this code calls for a div
with the ID buttonArea
, let's add that to our markup now:
Example 3.8. appmonitor2.html (excerpt)
<body>
<div id="buttonArea"></div>
</body>
The toggleStatusMessage
Method
Showing a button with the word "Start" or "Stop" on it might be all that programmers or engineers need to figure out the application's status, but most normal people need a message that's a little clearer and more obvious in order to work out what's going on with an application.
This upgraded version of the application will display a status message at the top of the page to tell the user about the overall state of the application (stopped or running), and the status of the polling process. To display the application status, we'll place a nice, clear message in the application's status bar that states App Status: Stopped or App Status: Running.
In our markup, let's insert the status message above where the button appears. We'll include only the "App Status" part of the message in our markup. The rest of the message will be inserted into a span with the ID currentAppState
:
Example 3.9. appmonitor2.html (excerpt)
<body>
<div id="statusMessage">App Status:
<span id="currentAppState"></span>
</div>
<div id="buttonArea"></div>
</body>
The toggleStatusMessage
method toggles between the words that can display inside the currentAppState
span:
Example 3.10. appmonitor2.js (excerpt)
this.toggleStatusMessage = function(stopped) {
var statSpan = document.getElementById('currentAppState');
var msg;
if (stopped) {
msg = 'Stopped';
}
else {
msg = 'Running';
}
if (statSpan.firstChild) {
statSpan.removeChild(statSpan.firstChild);
}
statSpan.appendChild(document.createTextNode(msg));
};
Once the UI is set up, the application is primed and ready to start polling and recording response times.
Checking your Work In Progress
Now that you've come this far, it would be nice to be able to see your work in action, right? Well, unfortunately, we've still got a lot of loose ends in our application — we've briefly mentioned a singleton class called Status but we haven't created it yet, and we still have event handlers left to write. 但是不要害怕! We can quickly get the application up and running with a few class and function stubs.
We'll start by creating that Status singleton class with one empty method.
Example 3.11. appmonitor2.js (excerpt)
var Status = new function() {
this.init = function() {
// don't mind me, I'm just a stub ...
};
}
Since the Status
class is used by the Monitor
class, we must declare Status
before Monitor
.
Then, we'll add our button's onclick
event handlers to the Monitor
class. We'll have them display alert dialogs so that we know what would be going on if there was anything happening behind the scenes.
Example 3.12. appmonitor2.js (excerpt)
this.pollServerStart = function() {
alert('This will start the application polling the server.');
};
this.pollServerStop = function() {
alert('This will stop the application polling the server.');
};
With these two simple stubs in place, your application should now be ready for a test-drive.
Figure 3.2. Humble beginnings
When you click the Start button in the display shown in Figure 3.2 you're presented with an alert box that promises greater things to come. Let's get started making good on those promises.
Polling the Server
The first step is to flesh out the Start button's onclick
event handler, pollServerStart
:
Example 3.13. appmonitor2.js (excerpt)
this.pollServerStart = function() {
var self = Monitor;
self.doPoll();
self.toggleAppStatus(false);
};
This code immediately calls the doPoll
method, which, like the app monitor we built in Chapter 2, Basic XMLHttpRequest, will be responsible for making an HTTP request to poll the server. Once the request has been sent, the code calls toggleAppStatus
, passing it false to indicate that polling is underway.
Where's the Poll Interval?
You might wonder why, after all this talk about setting a poll interval, our code jumps right in with a request to the server; where's the time delay? The answer is that we don't want a time delay on the very first request. If users click the button and there's a ten-second delay before anything happens, they'll think the app is broken. We want delays between all the subsequent requests that occur once the application is running, but when the user first clicks that button, we want the polling to start right away.
The only difference between doPoll
in this version of our app monitor and the one we saw in the last chapter is the use of self
to prefix the properties of the class, and the call to setTimeout
. 看一看:
Example 3.14. appmonitor2.js (excerpt)
this.doPoll = function() {
var self = Monitor;
var url = self.targetURL;
var start = new Date();
self.reqStatus.startProc();
self.start = start.getTime();
self.ajax.doGet(self.targetURL + '?start=' + self.start,
self.showPoll);
self.timeoutHand = setTimeout(self.handleTimeout,
self.timeoutThreshold * 1000);
};
Our call to setTimeout
instructs the browser to call handleTimeout
once the timeout threshold has passed. We're also keeping track of the interval ID that's returned, so we can cancel our call to handleTimeout
when the response is received by showPoll
.
Here's the code for the showPoll
method, which handles the response from the server:
Example 3.15. appmonitor2.js (excerpt)
this.showPoll = function(str) {
var self = Monitor;
var diff = 0;
var end = new Date();
clearTimeout(self.timeoutHand);
self.reqStatus.stopProc(true);
if (str == 'ok') {
end = end.getTime();
diff = (end - self.start) / 1000;
}
if (self.updatePollArray(diff)) {
self.printResult();
}
self.doPollDelay();
};
The first thing this method does is cancel the delayed call to handleTimeout
that was made at the end of doPoll
. After this, we tell our instance of the Status
class to stop its animation (we'll be looking at the details of this a little later).
After these calls, showPoll
checks to make sure that the response is ok, then calculates how long that response took to come back from the server. The error handling capabilities of the Ajax
class should handle errors from the server, so our script shouldn't return anything other than ok
… though it never hurts to make sure!
Once it has calculated the response time, showPoll
records that response time with updatePollArray
, then displays the result with printResult
. We'll look at both of these methods in the next section.
Finally, we schedule another poll in doPollDelay
— a very simple method that schedules another call to doPoll
once the poll interval has passed:
Example 3.16. appmonitor2.js (excerpt)
this.doPollDelay = function() {
var self = Monitor;
self.pollHand = setTimeout(self.doPoll,
self.pollInterval * 1000);
};
To check our progress up to this point, we'll need to add a few more stub methods. First, let's add startProc
and stopProc
to the Status
class:
Example 3.17. appmonitor2.js (excerpt)
var Status = new function() {
this.init = function() {
// don't mind me, I'm just a stub ...
};
this.startProc = function() {
// another stub function
};
this.stopProc = function() {
// another stub function
};
}
Let's also add a few stub methods to our Monitor
class:
Example 3.18. appmonitor2.js (excerpt)
this.handleTimeout = function() {
alert("Timeout!");
};
this.updatePollArray = function(responseTime) {
alert("Recording response time: " + responseTime);
};
this.printResult = function() {
// empty stub function
};
Now we're ready to test our progress. Open appmonitor2.html
in your web browser, click Start, and wait for fakeserver.php
to wake from its sleep and send ok back to your page.
You can expect one of two outcomes: either a response is received by your page, and you see a dialog similar to the one shown in Figure 3.3, or you see the timeout message shown in Figure 3.4.
Figure 3.3. A response received by your AJAX application
Don't worry if you receive the timeout message shown in Figure 3.4. Keep in mind that in our AJAX application, our timeout threshold is currently set to ten seconds, and that fakeserver.php
is currently sleeping for a randomly selected number of seconds between three and 12. If the random number is ten or greater, the AJAX application will report a timeout.
Figure 3.4. Your AJAX application giving up hope
At the moment, we haven't implemented a way to stop the polling, so you'll need to stop it either by reloading the page or closing your browser window.
Handling Timeouts
If you've run the code we've written so far, you've probably noticed that even when a timeout is reported, you see a message reporting the request's response time soon afterward. This occurs because handleTimeout is nothing but a simple stub at the moment. Let's look at building on that stub so we don't get this side-effect.
handleTimeout
is basically a simplified version ofshowPoll
: both methods are triggered by an asynchronous event (a call tosetTimeout
and an HTTP response received by anXMLHttpRequest
object respectively), both methods need to record the response time (in a timeout's case, this will be 0), both methods need to update the user interface, and both methods need to trigger the next call todoPoll
. Here's the code forhandleTimeout
:
Example 3.19. appmonitor2.js (excerpt)
this.handleTimeout = function() {
var self = Monitor;
if (self.stopPoll()) {
self.reqStatus.stopProc(true);
if (self.updatePollArray(0)) {
self.printResult();
}
self.doPollDelay();
}
};
Here, handleTimeout
calls stopPoll to stop our application polling the server. It records that a timeout occurred, updates the user interface, and finally sets up another call to doPoll
via doPollDelay
. We moved the code that stops the polling into a separate function because we'll need to revisit it later and beef it up. At present, the stopPoll
method merely aborts the HTTP request via the Ajax
class's abort
method; however, there are a few scenarios that this function doesn't handle. We'll address these later, when we create the complete code to stop the polling process, but for the purposes of handling the timeout, stopPoll
is fine.
Example 3.20. appmonitor2.js (excerpt)
this.stopPoll = function() {
var self = Monitor;
if (self.ajax) {
self.ajax.abort();
}
return true;
};
Now, when we reload our application, the timeouts perform exactly as we expect them to.
The Response Times Bar Graph
Now, to the meat of the new version of our monitoring app! We want the application to show a list of past response times, not just a single entry of the most recent one, and we want to show that list in a way that's quickly and easily readable. A running bar graph display is the perfect tool for the job.
The Running List in pollArray
All the response times will go into an array that's stored in the pollArray
property of the Monitor
class. We keep this array updated with the intuitively named updatePollArray
method. It's a very simple method that looks like this:
Example 3.21. appmonitor2.js (excerpt)
this.updatePollArray = function(pollResult) {
var self = Monitor;
self.pollArray.unshift(pollResult);
if (self.pollArray.length > self.maxPollEntries) {
self.pollArray.pop();
}
return true;
};
The code is very straightforward, although some of the functions we've used in it have slightly confusing names.
The unshift
method of an Array
object puts a new item in the very first element of the array, and shifts the rest of the array's contents over by one position, as shown in Figure 3.5.
Figure 3.5. Inserting fruit using unshift
When the array exceeds the user-defined maximum length, updatePollArray
truncates it by "popping" an item off the end. This is achieved by the pop
method, which simply deletes the last item of an array. (Note that the method name pop
may seem quite odd, but it makes more sense once you understand a data structure called a stack, which stores a number of items that can be accessed only in the reverse of the order in which they were added to the stack. We "push" an item onto a stack to add it, and "pop" an item from a stack to retrieve it. The pop
method was originally designed for developers who were using arrays as stacks, but here we've repurposed it simply to delete the last item in an array.) The reason why we append items to the top and remove items from the bottom of the array is that, in our display, we want the most recent entries to appear at the top, and older entries to gradually move down to the bottom.
Displaying the Results
Once we've updated the results in pollArray
, we can display them using the printResult
method. This is actually the cool part: the user will experience first-hand the difference between our AJAX application and an older-style app that requires an entire page refresh to update content.
Rendering Page Partials
In AJAX jargon, the chunk of the page that holds the list of response times is called a page partial. This refers to an area of a web page that's updated separately from the rest of the page.
Updating a chunk of a web page in response to an asynchronous request to the server is called "rendering a page partial."
The printResult
method iterates through pollArray
, and uses DOM methods to draw the list of poll results inside a div
with the ID pollResults
. We'll start by adding that div
to our markup:
Example 3.22. appmonitor2.html (excerpt)
<body>
<div id="statusMessage">App Status:
<span id="currentAppState"></span>
</div>
<div id="pollResults"></div>
<div id="buttonArea"></div>
</body>
Now we're ready for the printResult
method:
Example 3.23. appmonitor2.js (excerpt)
this.printResult = function() {
var self = Monitor;
var polls = self.pollArray;
var pollDiv = document.getElementById('pollResults');
var entryDiv = null;
var messageDiv = null;
var barDiv = null;
var clearAll = null;
var msgStr = '';
var txtNode = null;
while (pollDiv.firstChild) {
pollDiv.removeChild(pollDiv.firstChild);
}
for (var i = 0; i < polls.length; i++) {
if (polls[i] == 0) {
msgStr = '(Timeout)';
}
else {
msgStr = polls[i] + ' sec.';
}
entryDiv = document.createElement('div');
messageDiv = document.createElement('div');
barDiv = document.createElement('div');
clearAll = document.createElement('br');
entryDiv.className = 'pollResult';
messageDiv.className = 'time';
barDiv.className = 'bar';
clearAll.className = 'clearAll';
if (polls[i] == 0) {
messageDiv.style.color = '#933';
}
else {
messageDiv.style.color = '#339';
}
barDiv.style.width = (parseInt(polls[i] * 20)) + 'px';
messageDiv.appendChild(document.createTextNode(msgStr));
barDiv.appendChild(document.createTextNode('u00A0'));
entryDiv.appendChild(messageDiv);
entryDiv.appendChild(barDiv);
entryDiv.appendChild(clearAll);
pollDiv.appendChild(entryDiv);
}
};
There's quite a bit here, so let's look at this method step by step.
Example 3.24. appmonitor2.js (excerpt)
while (pollDiv.firstChild) {
pollDiv.removeChild(pollDiv.firstChild);
}
After initializing some variables, this method removes everything from pollDiv
: the while
loop uses removeChild
repeatedly to delete all the child nodes from pollDiv
.
Next comes a simple for loop that jumps through the updated array of results and displays them.
We generate a message for the result of each item in this array. As you can see below, timeouts (which are recorded as a 0) generate a message of (Timeout)
.
Example 3.25. appmonitor2.js (excerpt)
if (polls[i] == 0) {
msgStr = '(Timeout)';
}
else {
msgStr = polls[i] + ' sec.';
}
Next, we use DOM methods to add the markup for each entry in the list dynamically. In effect, we construct the following HTML in JavaScript for each entry in the list:
<div class="pollResult">
<div class="time" style="color: #339;">8.031 sec.</div>
<div class="bar" style="width: 160px;"> </div>
<br class="clearAll"/>
</div>
The width of the bar div
changes to reflect the actual response time, and timeouts are shown in red, but otherwise all entries in this list are identical. Note that you have to put something in the div
to cause its background color to display. Even if you give the div
a fixed width, the background color will not show if the div
is empty. This is annoying, but it's easy to fix: we can fill in the div
with a non-breaking space character.
Let's take a look at the code we'll use to insert this markup:
Example 3.26. appmonitor2.js (excerpt)
entryDiv = document.createElement('div');
messageDiv = document.createElement('div');
barDiv = document.createElement('div');
clearAll = document.createElement('br');
entryDiv.className = 'pollResult';
messageDiv.className = 'time';
barDiv.className = 'bar';
clearAll.className = 'clearAll';
if (polls[i] == 0) {
messageDiv.style.color = '#933';
}
else {
messageDiv.style.color = '#339';
}
barDiv.style.width = (parseInt(polls[i] * 20)) + 'px';
messageDiv.appendChild(document.createTextNode(msgStr));
barDiv.appendChild(document.createTextNode('u00A0'));
entryDiv.appendChild(messageDiv);
entryDiv.appendChild(barDiv);
entryDiv.appendChild(clearAll);
pollDiv.appendChild(entryDiv);
This code may seem complicated if you've never used DOM manipulation functions, but it's really quite simple. We use the well-named createElement
method to create elements; then we assign values to the properties of each of those element objects.
Just after the if
statement, we can see the code that sets the pixel width of the bar div
according to the number of seconds taken to generate each response. We multiply that time figure by 20 to get a reasonable width, but you may want to use a higher or lower number depending on how much horizontal space is available on the page.
To add text to elements, we use createTextNode
in conjunction with appendChild
, which is also used to place elements inside other elements.
createTextNode
and Non-breaking Spaces
In the code above, we create a non-breaking space using u00A0
. If we try to use the normal
entity here, createTextNode
will attempt to be "helpful" by converting the ampersand to &
; the result of this is that
is displayed on your page. The workaround is to use the escaped unicode non-breaking space: u00A0
.
Figure 3.6. The application starting to take shape
The last piece of the code puts all the div elements together, then places the pollResult div inside the pollResults div. Figure 3.6 shows the running application.
"Hold on a second," you may well be thinking. "Where's the bar graph we're supposed to be seeing?"
The first bar is there, but it's displayed in white on white, which is pretty useless. Let's make it visible through our application's CSS:
Example 3.27. appmonitor2.css (excerpt)
.time {
width: 6em;
float: left;
}
.bar {
background: #ddf;
float: left;
}
.clearBoth {
clear: both;
}
The main point of interest in the CSS is the float: left
declarations for the time
and bar div
elements, which make up the time listing and the colored bar in the bar graph. Floating them to the left is what makes them appear side by side. However, for this positioning technique to work, an element with the clearBoth
class must appear immediately after these two div
s.
This is where you can see AJAX in action. It uses bits and pieces of all these different technologies — XMLHttpRequest
, the W3C DOM, and CSS — wired together and controlled with JavaScript. Programmers often experience the biggest problems with CSS and with the practicalities of building interface elements in their code.
As an AJAX programmer, you can either try to depend on a library to take care of the CSS for you, or you can learn enough to get the job done. It's handy to know someone smart who's happy to answer lots of questions on the topic, or to have a good book on CSS (for example, SitePoint's The CSS Anthology: 101 Essential Tips, Tricks & Hacks ).
Figure 3.7. The beginnings of our bar graph
Now that our CSS is in place, we can see the bar graph in our application display, as Figure 3.7 illustrates.
Stopping the Application
The final action of the pollServerStart
method, after getting the app running, is to call toggleAppStatus
to toggle the appearance of the application. toggleAppStatus
changes the status display to App Status: Running, switches the Start button to a Stop button, and attaches the pollServerStop
method to the button's onclick
event.
The pollServerStop
method stops the ongoing polling process, then toggles the application back so that it looks like it's properly stopped:
Example 3.28. appmonitor2.js (excerpt)
this.pollServerStop = function() {
var self = Monitor;
if (self.stopPoll()) {
self.toggleAppStatus(true);
}
self.reqStatus.stopProc(false);
};
This code reuses the stopPoll
method we added earlier in the chapter. At the moment, all that method does is abort the current HTTP request, which is fine while we're handling a timeout. However, this method needs to handle two other scenarios as well.
The first of these scenarios occurs when the method is called during the poll interval (that is, after we receive a response to an HTTP request, but before the next request is sent). In this scenario, we need to cancel the delayed call to doPoll
.
The second scenario that this method must be able to handle arises when stopPoll
is called after it has sent a request, but before it receives the response. In this scenario, the timeout handler needs to be canceled.
As we keep track of the interval IDs of both calls, we can modify stopPoll
to handle these scenarios with two calls to clearTimeout
:
Example 3.29. appmonitor2.js (excerpt)
this.stopPoll = function() {
var self = Monitor;
clearTimeout(self.pollHand);
if (self.ajax) {
self.ajax.abort();
}
clearTimeout(self.timeoutHand);
return true;
};
Now, you should be able to stop and start the polling process just by clicking the Start/Stop button beneath the bar graph.
Status Notifications
The ability of AJAX to update content asynchronously, and the fact that updates may affect only small areas of the page, make the display of status notifications a critical part of an AJAX app's design and development. After all, your app's users need to know what the app is doing.
Back in the old days of web development, when an entire page had to reload in order to reflect any changes to its content, it was perfectly clear to end users when the application was communicating with the server. But our AJAX web apps can talk to the server in the background, which means that users don't see the complete page reload that would otherwise indicate that something was happening.
So, how will users of your AJAX app know that the page is communicating with the server? Well, instead of the old spinning globe or waving flag animations that display in the browser chrome, AJAX applications typically notify users that processing is under way with the aid of small animations or visual transitions. Usually achieved with CSS, these transitions catch users' eyes — without being distracting! — and provide hints about what the application is doing. An important aspect of the good AJAX app design is the development of these kinds of notifications.
The Status Animation
Since we already have at the top of our application a small bar that tells the user if the app is running or stopped, this is a fairly logical place to display a little more status information.
Animations like twirling balls or running dogs are a nice way to indicate that an application is busy — generally, you'll want to display an image that uses movement to indicate activity. However, we don't want to use a cue that's going to draw users' attention away from the list, or drive people to distraction as they're trying to read the results, so we'll just go with the slow, pulsing animation shown in Figure 3.8.
This animation has the added advantages of being lightweight and easy to implement in CSS — no Flash player is required, and there's no bulky GIF image to download frame by tedious frame.
The far right-hand side of the white bar is unused space, which makes it an ideal place for this kind of notification: it's at the top of the user interface, so it's easy to see, but it's off to the right, so it's out of the way of people who are trying to read the list of results.
Figure 3.8. Our pulsing status animation
To host this animation, we'll add a div
with the ID pollingMessage
just below the status message div
in our document:
Example 3.30. appmonitor2.html (excerpt)
<body>
<div id="statusMessage">App Status:
<span id="currentAppState"></span>
</div>
<div id="pollingMessage"></div>
<div id="pollResults"></div>
<div id="buttonArea"></div>
</body>
Add a CSS rule to your style sheet to position this div
:
Example 3.31. appmonitor2.css (excerpt)
#pollingMessage {
float: right;
width: 80px;
padding: 0.2em;
text-align: center;
}
This animation is now positioned to the right of the page.
When you open the page in your browser, you won't be able to see the animation — it's nothing but a white box on a white background at the moment. If you'd like to, add some content to pollingMessage
to see where it's positioned.
setInterval
and Loss of Scope
The JavaScript setInterval is an obvious and easy way to handle a task that occurs repeatedly — for instance, to control a pulsing animation.
All the CSS gyrations with setInterval
result in some fairly interesting and bulky code. So, as I mentioned before, it makes sense to put the code for the status animation into its own class — Status
— that we can reference and use from the Monitor
class.
Some of the clever developers reading this may already have guessed that setInterval
suffers from the same loss-of-scope problems as setTimeout
: the object keyword this
becomes lost. Since we have to deal with only one status animation in our monitoring application, it makes sense to take the expedient approach, and make our Status
class a singleton class, just as we did for the Monitor
class.
Setting Up Status
Let's start by adding some properties to the Status stub we've already written, in order to get the previous code working:
Example 3.32. appmonitor2.js (excerpt)
var Status = new function() {
this.currOpacity = 100;
this.proc = 'done'; // 'proc', 'done' or 'abort'
this.procInterval = null;
this.div = null;
this.init = function() {
// don't mind me, I'm just a stub ...
};
this.startProc = function() {
// another stub function
};
this.stopProc = function() {
// another stub function
};
}
The Status
object has four properties:
- The
currOpacity
property tracks the opacity of thepollingMessage
div
. We usesetInterval
to change the opacity of thisdiv
rapidly, which produces the pulsing and fading effect. - The
proc
property is a three-state switch that indicates whether an HTTP request is currently in progress, has been completed successfully, or was aborted before completion. - The
procInterval
property is for storing the interval ID for thesetInterval
process that controls the animation. We'll use it to stop the running animation. - The
div
property is a reference to thepollingMessage
div
. TheStatus
class manipulates thepollingMessage
div
's CSS properties to create the animation.
Initialization
An init
method is needed to bind the div
property to pollingMessage
:
Example 3.33. appmonitor2.js (excerpt)
this.init = function() {
var self = Status;
self.div = document.getElementById('pollingMessage');
self.setAlpha();
};
The init
method also contains a call to a method named setAlpha
, which is required for an IE workaround that we'll be looking at a bit later.
Internet Explorer Memory Leaks
DOM element references (variables that point to div
, td
, or span
elements and the like) that are used as class properties are a notorious cause of memory leaks in Internet Explorer. If you destroy an instance of a class without clearing such properties (by setting them to null
), memory will not be reclaimed.
Let's add to our Monitor
class a cleanup method that handles the window.onunload
event, like so:
Example 3.34. appmonitor2.js (excerpt)
window.onunload = Monitor.cleanup;
This method cleans up the Status
class by calling that class's cleanup
method and setting the reqStatus
property to null
:
Example 3.35. appmonitor2.js (excerpt)
this.cleanup = function() {
var self = Monitor;
self.reqStatus.cleanup();
self.reqStatus = null;
};
The cleanup
method in the Status
class does the IE housekeeping:
Example 3.36. appmonitor2.js (excerpt)
this.cleanup = function() {
Status.div = null;
};
If we don't set that div
reference to null
, Internet Explorer will keep the memory it allocated to that variable in a death grip, and you'll see memory use balloon each time you reload the page.
In reality, this wouldn't be much of a problem for our tiny application, but it can become a serious issue in large web apps that have a lot of DHTML. It's good to get into the habit of cleaning up DOM references in your code so that this doesn't become an issue for you.
The displayOpacity
Method
The central piece of code in the Status
class lives in the displayOpacity
method. This contains the browser-specific code that's necessary to change the appropriate CSS properties of the pollingMessage
div
. 这是代码:
Example 3.37. appmonitor2.js (excerpt)
this.displayOpacity = function() {
var self = Status;
var decOpac = self.currOpacity / 100;
if (document.all && typeof window.opera == 'undefined') {
self.div.filters.alpha.opacity = self.currOpacity;
}
else {
self.div.style.MozOpacity = decOpac;
}
self.div.style.opacity = decOpac;
};
The currOpacity
property of the object represents the opacity to which the pollingMessage
div
should be set. Our implementation uses an integer scale ranging from 0 to 100, which is employed by Internet Explorer, rather than the fractional scale from zero to one that's expected by Mozilla and Safari. This choice is just a personal preference; if you prefer to use fractional values, by all means do.
In the method, you'll see a test for document.all
— a property that's supported only by IE and Opera — and a test for window.opera
, which, unsurprisingly, is supported only by Opera. As such, only IE should execute the if clause of this if statement. Inside this IE branch of the if
statement, the proprietary alpha.opacity
property is used to set opacity, while in the else
clause, we use the older MozOpacity
property, which is supported by older Mozilla-based browsers.
Finally, this method sets the opacity in the standards-compliant way: using the opacity
property, which should ultimately be supported in all standards-compliant browsers.
IE Gotchas
Internet Explorer version 6, being an older browser, suffers a couple of issues when trying to render opacity-based CSS changes.
Fortunately, the first of these is easily solved by an addition to our pollingMessage
CSS rule:
Example 3.38. appmonitor2.css (excerpt)
#pollingMessage {
float: right;
width: 80px;
padding: 0.2em;
text-align: center;
background: #fff;
}
The addition of the background property fixes the first specific problem with Internet Explorer. We must set the background color of an element if we want to change its opacity in IE, or the text will display with jagged edges. Note that setting background to transparent will not work: it must be set to a specific color.
The second problem is a little trickier if you want your CSS files to be valid. IE won't let you change the style.alpha.opacity
unless it's declared in the style sheet first. Now, if you don't mind preventing your style sheets from being passed by the W3C validator, it's easy to fix this problem by adding another declaration:
Example 3.39. appmonitor2.css (excerpt)
#pollingMessage {
float: right;
width: 80px;
padding: 0.2em;
text-align: center;
background: #fff;
filter: alpha(opacity = 100);
}
Unfortunately, this approach generates CSS warnings in browsers that don't support that proprietary property, such as Firefox 1.5, which displays CSS warnings in the JavaScript console by default. A solution that's better than inserting IE-specific style information into your global style sheet is to use JavaScript to add that declaration to the pollingMessage
div
's style
attribute in IE only. That's what the setAlpha
method that's called in init
achieves. Here's the code for that method:
Example 3.40. appmonitor2.js (excerpt)
this.setAlpha = function() {
var self = Status;
if (document.all && typeof window.opera ==
'undefined') {
var styleSheets = document.styleSheets;
for (var i = 0; i < styleSheets.length; i++) {
var rules = styleSheets[i].rules;
for (var j = 0; j < rules.length; j++) {
if (rules[j].selectorText ==
'#pollingMessage') {
rules[j].style.filter =
'alpha(opacity = 100)';
return true;
}
}
}
}
return false;
};
This code, which executes only in Internet Explorer, uses the document.styleSheets
array to iterate through each style sheet that's linked to the current page. It accesses the rules in each of those style sheets using the rules
property, and finds the style we want by looking at the selectorText
property. Once it has the right style in the rules
array, it gives the filter
property the value it needs to change the opacity.
Opacity in Opera?
Unfortunately, at the time of writing, even the latest version of Opera (version 8.5) doesn't support CSS opacity, so such an animation does not work in that browser. However, this feature is planned for Opera version 9.
Running the Animation
The code for the processing animation consists of five methods: the first three control the "Processing …" animation, while the remaining two control the "Done" animation. The three methods that control the "Processing …" animation are:
-
startProc
, which sets up the "Processing …" animation and schedules repeated calls todoProc
withsetInterval
-
doProc
, which monitors the properties of this class and sets the current frame of the "Processing …" animation appropriately -
stopProc
, which signals that the "Processing …" animation should cease
The two that control the "Done" animation are:
-
startDone
sets up the "Done" animation and schedules repeated calls todoDone
withsetInterval
-
doDone
sets the current frame of the "Done" animation and terminates the animation once it's completed
Starting it Up
Setting the animation up and starting it are jobs for the startProc
method:
Example 3.41. appmonitor2.js (excerpt)
this.startProc = function() {
var self = Status;
self.proc = 'proc';
if (self.setDisplay(false)) {
self.currOpacity = 100;
self.displayOpacity();
self.procInterval = setInterval(self.doProc, 90);
}
};
After setting the proc
property to proc
(processing), this code calls the setDisplay
method, which sets the color and content of the pollingMessage
div
. We'll take a closer look at setDisplay
next.
Once the code sets the color and content of the pollingMessage
div
, it initializes the div
's opacity to 100 (completely opaque) and calls displayOpacity
to make this setting take effect.
Finally, this method calls setInterval
to schedule the next step of the animation process. Note that, as with setTimeout
, the setInterval
call returns an interval ID. We store this in the procInterval
property so we can stop the process later.
Both the "Processing …" and "Done" animations share the setDisplay
method:
Example 3.42. appmonitor2.js (excerpt)
this.setDisplay = function(done) {
var self = Status;
var msg = '';
if (done) {
msg = 'Done';
self.div.className = 'done';
}
else {
msg = 'Processing...';
self.div.className = 'processing';
}
if (self.div.firstChild) {
self.div.removeChild(self.div.firstChild);
}
self.div.appendChild(document.createTextNode(msg));
return true;
};
Since the only differences between the "Processing …" and "Done" states of the pollingMessage
div
are its color and text, it makes sense to use this common function to toggle between the two states of the pollingMessage
div
. The colors are controlled by assigning classes to the pollingMessage
div
, so we'll need to add CSS class rules for the done
and processing
classes to our style sheet:
Example 3.43. appmonitor2.css (excerpt)
.processing {
color: #339;
border: 1px solid #339;
}
.done {
color:#393;
border:1px solid #393;
}
Making it Stop
Stopping the animation smoothly requires some specific timing. We don't want the animation to stop abruptly right in the middle of a pulse. We want to stop it in the natural break, when the "Processing …" image's opacity is down to zero.
So the stopProc
method for stopping the animation doesn't actually stop it per se — it just sets a flag to tell the animation process that it's time to stop when it reaches a convenient point. This is a lot like the phone calls received by many programmers at the end of the day from wives and husbands reminding them to come home when they get to a logical stopping point in their code.
Since very little action occurs here, the method is pretty short:
Example 3.44. appmonitor2.js (excerpt)
this.stopProc = function(done) {
var self = Status;
if (done) {
self.proc = 'done';
}
else {
self.proc = 'abort';
}
};
This method does have to distinguish between two types of stopping: a successfully completed request ( done
) and a request from the user to stop the application ( abort
).
The doProc
method uses this flag to figure out whether to display the "Done" message, or just to stop.
Running the Animation with doProc
The doProc
method, which is invoked at 90 millisecond intervals, changes the opacity of the pollingMessage
div
to produce the pulsing effect of the processing animation. 这是代码:
Example 3.45. appmonitor2.js (excerpt)
this.doProc = function() {
var self = Status;
if (self.currOpacity == 0) {
if (self.proc == 'proc') {
self.currOpacity = 100;
}
else {
clearInterval(self.procInterval);
if (self.proc == 'done') {
self.startDone();
}
return false;
}
}
self.currOpacity = self.currOpacity - 10;
self.displayOpacity();
};
This method is dead simple — its main purpose is simply to reduce the opacity of the pollingMessage
div
by 10% every time it's called.
The first if statement looks to see if the div
has completely faded out. If it has, and the animation is still supposed to be running, it resets the opacity to 100 (fully opaque). Executing this code every 90 milliseconds produces a smooth effect in which the pollingMessage
div
fades out, reappears, and fades out again — the familiar pulsing effect that shows that the application is busy doing something.
If the animation is not supposed to continue running, we stop the animation by calling clearInterval
, then, if the proc property is done, we trigger the "Done" animation with a call to startDone
.
Starting the "Done" Animation with startDone
The startDone
method serves the same purpose for the "Done" animation that the startProc
method serves for the "Processing …" animation. It looks remarkably similar to startProc
, too:
Example 3.46. appmonitor2.js (excerpt)
this.startDone = function() {
var self = Status;
if (self.setDisplay(true)) {
self.currOpacity = 100;
self.displayOpacity();
self.procInterval = setInterval(self.doDone, 90);
}
};
This time, we pass true
to setDisplay
, which will change the text to "Done" and the color to green.
We then set up calls to doDone
with setInterval
, which actually performs the fadeout.
The Final Fade
The code for doDone
is significantly simpler than the code for doProc
. It doesn't have to process continuously until told to stop, like doProc
does. It just keeps on reducing the opacity of the pollingMessage
div
by 10% until it reaches zero, then stops itself. Pretty simple stuff:
Example 3.47. appmonitor2.js (excerpt)
this.doDone = function() {
var self = Status;
if (self.currOpacity == 0) {
clearInterval(self.procInterval);
}
self.currOpacity = self.currOpacity - 10;
self.displayOpacity();
};
Figure 3.9. The application with a pulsing status indicator
Finally, we're ready to test this code in our browser. Open appmonitor2.html
in your browser, click the Start button, and you should see a pulsing Processing … message near the top right-hand corner of the browser's viewport, like the one shown in Figure 3.9.
Be Careful with that Poll Interval!
Now that we have an animation running in the page, we need to be careful that we don't start the animation again before the previous one stops. For this reason, it's highly recommended that you don't set POLL_INTERVAL
to anything less than two seconds.
Styling the Monitor
Now that we've got our application up and running, let's use CSS to make it look good. We'll need to add the following markup to achieve our desired layout:
Example 3.48. appmonitor2.html (excerpt)
<body>
<div id="wrapper">
<div id="main">
<div id="status">
<div id="statusMessage">App Status:
<span id="currentAppState"></span>
</div>
<div id="pollingMessage"></div>
<br class="clearBoth" />
</div>
<div id="pollResults"></div>
<div id="buttonArea"></div>
</div>
</div>
</body>
As you can see, we've added three div
s from which we can hang our styles, and a line break to clear the floated application status message and animation. The completed CSS for this page is as follows; the styled interface is shown in Figure 3.10:
Example 3.49. appmonitor2.css
body, p, div, td, ul {
font-family: verdana, arial, helvetica, sans-serif;
font-size:12px;
}
#wrapper {
padding-top: 24px;
}
#main {
width: 360px;
height: 280px;
padding: 24px;
text-align: left;
background: #eee;
border: 1px solid #ddd;
margin:auto;
}
#status {
width: 358px;
height: 24px;
padding: 2px;
background: #fff;
margin-bottom: 20px;
border: 1px solid #ddd;
}
#statusMessage {
font-size: 11px;
float: left;
height: 16px;
padding: 4px;
text-align: left;
color: #999;
}
#pollingMessage {
font-size: 11px;
float: right;
width: 80px;
height: 14px;
padding: 4px;
text-align: center;
background: #fff;
}
#pollResults {
width: 360px;
height: 210px;
}
#buttonArea {
text-align: center;
}
.pollResult {
padding-bottom: 4px;
}
.time {
font-size: 11px;
width: 74px;
float: left;
}
.processing {
color: #339;
border: 1px solid #333399;
}
.done {
color: #393;
border: 1px solid #393;
}
.bar {
background: #ddf;
float: left;
}
.inputButton {
width: 8em;
height: 2em;
}
.clearBoth {
clear: both;
}
Figure 3.10. The completed App Monitor
摘要
Our first working application showed how AJAX can be used to make multiple requests to a server without the user ever leaving the currently loaded page. It also gave a fairly realistic picture of the kind of complexity we have to deal with when performing multiple tasks asynchronously. A good example of this complexity was our use of setTimeout
to time the XMLHttpRequest requests. This example provided a good opportunity to explore some of the common problems you'll encounter as you develop AJAX apps, such as loss of scope and connection timeouts, and provided practical solutions to help you deal with them.
That's it for this excerpt from Build Your Own AJAX Web Applications — don't forget, you can download this article in .pdf format . The book has eight chapters in total, and by the end of it, readers will have built numerous fully functioning web apps including an online chess game that multiple players can play in real time — the book's Table of Contents has the full details.
From: https://www.sitepoint.com/build-your-own-ajax-web-apps/