jQuery2 高级教程(六)
十五、使用 Ajax:第二部分
在本章中,我将向您展示如何使用低级的 jQuery Ajax API(应用编程接口)。术语低级意味着在请求的内部寻找,但事实并非如此。我在这一章中描述的方法没有在第十四章中描述的那些方法方便,但是当简写和方便的方法所使用的配置不能很好地完成工作时,你可以多花一点力气来配置请求的细节。表 15-1 对本章进行了总结。
表 15-1 。章节总结
| 问题 | 解决办法 | 列表 |
|---|---|---|
| 用底层 API 进行 Ajax 调用 | 使用ajax方法 | one |
以类似于本机XMLHttpRequest对象的方式获取请求的细节 | 使用jqXHR方法 | Two |
| 指定 Ajax 请求的 URL | 使用url设置 | three |
| 为请求指定 HTTP 方法 | 使用type设置 | four |
| 响应成功的请求 | 使用success设置 | five |
| 响应不成功的请求 | 使用error设置 | six |
| 响应已完成的请求,无论成功与否 | 使用complete设置 | 7, 8 |
| 在发送请求之前配置请求 | 使用beforeSend设置 | nine |
| 指定多个函数来处理成功、不成功或已完成的请求 | 为success、error或complete设置指定一组函数 | Ten |
在success、error和complete设置的函数中,指定将分配给this变量的元素 | 使用context设置 | Eleven |
| 响应所有 Ajax 请求的事件 | 使用全局事件方法 | Twelve |
| 指定请求是否会导致触发全局事件 | 使用global设置 | Thirteen |
| 设置请求的超时时间 | 使用timeout设置 | Fourteen |
| 向请求添加标题 | 使用headers设置 | Fourteen |
| 指定为服务器设置的内容类型 | 使用contentType割台 | Fifteen |
| 指定请求是同步执行还是异步执行 | 使用async设置 | Sixteen |
| 忽略未更改的数据 | 使用ifModified设置 | Seventeen |
| 响应服务器发送的 HTTP 状态代码 | 使用statusCode设置 | Eighteen |
| 清理响应数据 | 使用dataFilter设置 | Nineteen |
| 控制数据的转换方式 | 使用converters设置 | Twenty |
| 为所有 Ajax 请求定义一个通用配置 | 使用ajaxSetup方法 | Twenty-one |
| 动态更改单个请求的配置 | 使用ajaxPrefilter方法 | Twenty-two |
自上一版以来,JQUERY 发生了变化
从 jQuery 1.9/2.0 开始,为 Ajax 全局事件设置处理程序的方法只能在document对象上调用(在早期的 jQuery 版本中,这些方法可以在任何元素上使用)。有关这些方法的详细信息,请参见“使用全局 Ajax 事件”一节。
用低级 API 发出简单的 Ajax 请求
使用低级 API 发出请求并不比使用我在第十四章的中展示的速记和便利方法复杂多少。不同之处在于,您可以配置请求的许多不同方面,并在执行请求时获得关于请求的更多信息。处于底层 API 核心的方法是ajax,清单 15-1 中的简单演示了它的用法。
清单 15-1 。使用 ajax 方法
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<script src="jquery.validate.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" data-price="{{price}}" data-stock="{{stocklevel}}"
value="0" required />
</div>
{{/flowers}}
</script>
<script type="text/javascript">
$(document).ready(function () {
$.ajax("mydata.json", {
success: function (data) {
var tmplElems = $("#flowerTmpl")
.template({flowers: data}).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
});
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post" action="http://node.jacquisflowershop.com/order">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow">
</div>
<div id="row2"class="drow">
</div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
通过传递想要请求的 URL 和 map 对象来使用ajax方法,map 对象的属性定义了一组键/值对,每个键/值对为请求配置一个设置。
注本章依赖于第十四章中使用的相同
Node.js脚本。
在这个例子中,我的 map 对象有一个属性——success——指定请求成功时调用的函数。我从服务器请求mydata.json文件,并使用它和一个数据模板来创建元素并将其插入到文档中,就像我在上一章中用速记方法所做的一样。默认情况下,ajax方法会发出一个 HTTP get请求,这意味着该示例相当于使用了get或getJSON方法,我在第十四章中向您展示过。(我将在“发出 POST 请求”一节中向您展示如何创建POST请求。)
有许多设置是可用的,我将在本章的剩余部分解释它们,以及 jQuery 提供的使 Ajax 更容易使用的方法。
了解 jqXHR 对象
由ajax方法返回的结果是一个jqXHR对象,您可以用它来获得 Ajax 请求的细节并与之交互。jqXHR对象是XMLHttpRequest对象的超集,后者被定义为万维网联盟(W3C) 标准的一部分,该标准支持浏览器对 Ajax 的支持,适用于 jQuery 延迟对象特性,我在第三十五章中对此进行了描述。
对于大多数 Ajax 请求,您可以简单地忽略jqXHR对象,这正是我建议您做的。当您需要更多关于服务器响应的信息时,jqXHR对象非常有用。表 15-2 描述了jqXHR对象的成员。
表 15-2 。jqXHR 成员
| 成员 | 描述 |
|---|---|
readyState | 返回请求在其生命周期中从未发送(值0)到完成(值4)的进度 |
status | 返回服务器发回的 HTTP 状态代码 |
statusText | 返回状态代码的文本描述 |
responseXML | 如果是 XML 文档,则返回响应 |
responseText | 以字符串形式返回响应 |
setRequestHeader(name, value) | 在请求上设置标头 |
getAllResponseHeaders() | 将响应中的所有标头作为单个字符串返回 |
getResponseHeader(name) | 返回指定响应头的值 |
abort() | 终止请求 |
提示
jqXHR对象可以用来配置 Ajax 请求,但是使用ajax方法的配置选项更容易做到这一点,我将在“...
使用 jQuery 时,您会在几个地方遇到jqXHR对象。正如我所说,第一个是来自ajax方法的结果,如清单 15-2 所示。
清单 15-2 。使用 jqXHR 对象
...
<script type="text/javascript">
$(document).ready(function () {
var jqxhr =$.ajax("mydata.json", {
success: function (data) {
var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
});
var timerID = setInterval(function () {
console.log("Status: " + jqxhr.status + " " + jqxhr.statusText);
if (jqxhr.readyState == 4) {
console.log("Request completed: " + jqxhr.responseText);
clearInterval(timerID);
}
}, 100);
});
</script>
...
在这个清单中,我将来自ajax方法的结果赋给一个名为jqxhr的变量,并使用setInterval方法每隔 100 毫秒将关于请求的信息写入控制台。使用ajax方法的结果不会改变异步执行请求的事实,所以在使用jqXHR对象时需要小心。我使用readyState属性来检查请求的状态(4的值表示请求已经完成),并将响应从服务器写入控制台。该脚本生成以下控制台输出(尽管根据您的浏览器配置,您可能会看到稍微不同的内容):
Status: 200 OK
Request completed: [{"name":"Aster","product":"aster","stocklevel":"10","price":"2.99"}, {"name":"Daffodil","product":"daffodil","stocklevel":"12","price":"1.99"}, {"name":"Rose","product":"rose","stocklevel":"2","price":"4.99"}, {"name":"Peony","product":"peony","stocklevel":"0","price":"1.50"}, {"name":"Primula","product":"primula","stocklevel":"1","price":"3.12"}, {"name":"Snowdrop","product":"snowdrop","stocklevel":"15","price":"0.99"}]
提示我很少使用
jqXHR对象,当它是ajax方法的结果时也从不使用。如果我想使用jqXHR对象(通常是为了从服务器获得关于响应的额外信息),那么我通常通过事件处理程序设置来实现,我在“处理 Ajax 回调”一节中对此进行了描述他们给我一个关于请求状态的上下文,而不是让我轮询请求状态。
设置请求 URL
作为将请求的 URL 作为参数传递给ajax方法的替代方法,您可以在地图对象中定义一个url属性,如清单 15-3 所示。
清单 15-3 。使用 url 属性
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
success: function (data) {
var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
});
});
</script>
...
发出发布请求
使用type设置为请求设置 HTTP 方法。默认情况下是发出GET请求,就像前面的例子一样。清单 15-4 显示了使用ajax方法创建一个POST请求并提交表单数据给服务器。
清单 15-4 。用 ajax 方法创建 POST 请求
...
<script id="totalTmpl" type="text/x-handlebars-template">
<div id="totalDiv" style="clear: both; padding: 5px">
<div style="text-align: center">Total Items:
<span id=total>{{total}}</span></div>
</div>
</script>
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
success: function (data) {
var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
});
$("button").click(function (e) {
$.ajax({
url: $("form").attr("action"),
data: $("form").serialize(),
type: "post",
success: processServerResponse
})
e.preventDefault();
})
function processServerResponse(data) {
var inputElems = $("div.dcell").hide();
for (var prop in data) {
var filtered = inputElems.has("input[name=' + prop + ']")
.appendTo("#row1").show();
}
$("#buttonDiv").remove();
$("#totalTmpl").template(data).appendTo("body");
}
});
</script>
...
除了type我还用过几个设置。为了指定POST请求的目标,我使用了url属性,该属性来自文档中form元素的目标。我使用data属性指定要发送的数据,我使用serialize方法设置该属性(在第三十四章的中描述)。
超越获取和发布
您可以使用type属性来指定任何 HTTP 方法,但是您可能很难使用除了GET或POST之外的任何方法,因为许多防火墙和应用服务器被配置为丢弃其他类型的请求。如果您想使用其他 HTTP 方法,那么您可以发出一个POST请求,但是要添加X-HTTP-Method-Override 头,将其设置为您想使用的方法,如下所示:
X-HTTP-Method-Override: PUT
这个约定被 web 应用框架广泛支持,并且是创建 RESTful web 应用的一种常见方式,你可以在http://en.wikipedia.org/wiki/Representational_state_transfer了解更多。有关如何在 jQuery Ajax 请求上设置头的详细信息,请参见“设置超时和头”一节。
处理 Ajax 回调
有几个属性允许您为 Ajax 请求生命周期中的关键点指定回调函数。当我在清单 15-4 中使用success属性时,你已经看到了其中一个回调。表 15-3 描述了用于设置每个回调的属性。
表 15-3 。Ajax 事件属性
| 环境 | 描述 |
|---|---|
beforeSend | 指定一个在 Ajax 请求启动前将被调用的函数 |
complete | 指定 Ajax 请求成功或失败时将调用的函数 |
error | 指定 Ajax 请求失败时将调用的函数 |
success | 指定 Ajax 请求成功时将调用的函数 |
提示表 15-3 中描述的设置与本地回调相关,这意味着它们处理单个 Ajax 请求。您还可以使用一系列的全局事件,我在“使用全局 Ajax 事件”一节中对此进行了描述
处理成功的请求
当我演示使用success属性 时,我省略了函数中的几个参数:描述请求结果的状态消息和一个jqXHR对象。清单 15-5 展示了接受这些参数的函数的使用。
清单 15-5 。接收成功函数的所有参数
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
success: function (data, status, jqxhr) {
console.log("Status: " + status);
console.log("jqXHR Status: " + jqxhr.status + " " + jqxhr.statusText);
console.log(jqxhr.getAllResponseHeaders());
var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
});
});
</script>
...
status参数是描述请求结果的字符串。由success属性指定的回调函数只在结果成功时执行,因此这个参数通常具有值success。当您使用ifModified设置时会出现异常,我在“忽略未修改的数据”一节中对此进行了描述其他 Ajax 事件的回调函数遵循相同的模式,这个参数在其他一些事件中更有用。
最后一个参数是一个jqXHR对象。在处理jqXHR对象之前,您不必轮询请求的状态,因为您知道只有当请求成功完成时,该函数才会被执行。在清单 15-5 中,我使用了jqXHR对象来获取状态信息和服务器包含在响应中的消息头,并将它们写入控制台。此示例产生以下结果(尽管根据您使用的 web 服务器,您会看到一组不同的标题):
Status: success
jqXHR Status: 200 OK
Date: Thu, 20 Jun 2013 12:06:30 GMT
Last-Modified: Wed, 19 Jun 2013 16:29:49 GMT
Server: Microsoft-IIS/7.5
X-Powered-By: ASP.NET
ETag: "b680cf37a6dce1:0"
Content-Type: application/json
Cache-Control: no-cache
Accept-Ranges: bytes
Content-Length: 405
处理错误
error属性 指定当请求失败时要调用的回调函数。清单 15-6 提供了一个演示。
清单 15-6 。使用错误属性
...
<style type="text/css">
.error {color: red; border: medium solid red; padding: 4px;
margin: auto; width: 200px; text-align: center}
</style>
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "NoSuchFile.json",
success: function (data, status, jqxhr) {
var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
},
error: function (jqxhr, status, errorMsg) {
$("<div>").addClass("error")
.text("Status: " + status + " Error: " + errorMsg)
.insertAfter("h1");
}
});
});
</script>
...
在清单 15-6 中,我请求了一个名为NoSuchFile.json 的文件,这个文件在 web 服务器上并不存在。这确保了请求会失败,我用error属性指定的回调函数会被调用。
传递给error回调函数的参数是一个jqXHR对象、一条状态消息和来自服务器响应的错误消息。在清单中,我使用error回调向文档添加一个div元素,显示status和errorMsg参数的值,如图 15-1 中的所示。
图 15-1 。显示错误信息
status参数可以是表 15-4 中显示的值之一。
表 15-4 。错误状态值
| 环境 | 描述 |
|---|---|
abort | 表示请求被中止(使用jqXHR对象) |
error | 表示一般错误,通常由服务器报告 |
parsererror | 指示无法解析服务器返回的数据 |
timeout | 表示请求在服务器响应前超时 |
errorMsg参数的值根据status而变化。当status为error时,那么errorMsg将被设置为服务器响应的文本部分。因此,在这个例子中,来自服务器的响应是404 Not Found,因此errorMsg被设置为Not Found。
当status为timeout时,errorMsg的值也将为timeout。您可以使用timeout设置来指定请求超时之前的时间段,我在“设置超时和头”一节中对此进行了描述
当status是parsererror时,那么errorMsg将包含问题的细节。当数据格式不正确或服务器为数据返回错误的 MIME 类型时,会出现此错误。(您可以使用dataType设置覆盖数据类型。)最后,当请求是abort时,状态和errorMsg值都将是abort。
提示虽然我已经在文档中显示了
status和errorMsg值,但这通常对用户没有帮助,因为这些消息需要对 web 应用内部发生的事情有所了解,并且它们不包含如何解决问题的说明。
处理已完成的请求
属性 指定了一个函数,当 Ajax 请求完成时将调用这个函数,不管它是成功还是失败。清单 15-7 提供了一个演示。
清单 15-7 。使用完整属性
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
success: function (data, status, jqxhr) {
var tmplElems = $("#flowerTmpl").template({flowers: data}).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
},
error: function (jqxhr, status, errorMsg) {
$("<div>").addClass("error")
.text("Status: " + status + " Error: " + errorMsg)
.insertAfter("h1");
},
complete: function (jqxhr, status) {
console.log("Completed: " + status);
}
});
});
</script>
...
由complete属性指定的回调函数在由success和error属性指定的函数之后被调用*。jQuery 将jqXHR对象和一个状态字符串传递给回调函数。状态字符串将被设置为表 15-5 中显示的值之一。*
表 15-5 。Ajax 事件设置
| 环境 | 描述 |
|---|---|
abort | 表示请求被中止(使用jqXHR对象) |
error | 表示一般错误,通常由服务器报告 |
notmodified | 表示所请求的内容自上次请求以来没有被修改过(有关更多详细信息,请参见“忽略未修改的数据”一节) |
parsererror | 指示无法解析服务器返回的数据 |
success | 指示请求成功完成 |
timeout | 表示请求在服务器响应前超时 |
您可能想使用complete设置来指定一个可以处理请求所有结果的函数,但是这样做意味着您不能从 jQuery 处理数据和错误的方式中获益。更好的方法是使用success和error设置,并仔细组织公共函数的参数,如清单 15-8 所示。
清单 15-8 。使用单个函数处理所有请求结果
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
success: function (data, status, jqxhr) {
handleResponse(status, data, null, jqxhr);
},
error: function (jqxhr, status, errorMsg) {
handleResponse(status, null, errorMsg, jqxhr);
}
});
function handleResponse(status, data, errorMsg, jqxhr) {
if (status == "success") {
var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
} else {
$("<div>").addClass("error")
.text("Status: " + status + " Error: " + errorMsg)
.insertAfter("h1");
}
}
});
</script>
...
在发送请求之前配置请求
beforeSend属性 允许您指定一个在请求开始之前将被调用的函数。这使您有机会进行最后的配置,补充或覆盖传递给ajax方法的设置,如果您对多个请求使用相同的基本设置对象,这将非常有用。清单 15-9 展示了beforeSend属性的使用。
清单 15-9 。使用 beforeSend 属性
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "NoSuchFile.json",
success: function (data, status, jqxhr) {
handleResponse(status, data, null, jqxhr);
},
error: function (jqxhr, status, errorMsg) {
handleResponse(status, null, errorMsg, jqxhr);
},
beforeSend: function (jqxhr, settings) {
settings.url = "mydata.json";
}
});
function handleResponse(status, data, errorMsg, jqxhr) {
if (status == "success") {
var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
} else {
$("<div>").addClass("error")
.text("Status: " + status + " Error: " + errorMsg)
.insertAfter("h1");
}
}
});
</script>
...
传递给回调函数的参数是传递给ajax方法的jqXHR对象和设置对象。在清单 15-9 中,我使用了url设置来指定 Ajax 请求的 URL,覆盖了url属性的值。
指定多个事件处理函数
我只展示了一个回调函数来响应 Ajax 请求,但是您可以将success、error、complete和beforeStart属性设置为一个函数数组,当相应的事件被触发时,它们中的每一个都将被执行。清单 15-10 提供了一个演示。
清单 15-10 。指定多个事件处理功能
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
success: [processData, reportStatus],
});
function processData(data, status, jqxhr) {
var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
function reportStatus(data, status, jqxhr) {
console.log("Status: " + status + " Result code: " + jqxhr.status);
}
});
</script>
...
在清单 15-10 的中,我将success属性设置为一个包含两个函数名的数组,其中一个函数名使用数据将元素添加到文档中,另一个函数名将信息打印到控制台。
设置事件的上下文
context属性 允许您指定一个元素,该元素将在事件功能启用时分配给this变量。这对于定位文档中的元素很有用,而不必在处理函数中选择它们。清单 15-11 给出了一个演示。
清单 15-11 。使用上下文属性
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
context: $("h1"),
success: function (data, status, jqxhr) {
var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
},
complete: function (jqxhr, status) {
var color = status == "success" ? "green" : "red";
this.css("border", "thick solid " + color);
}
});
});
</script>
...
在清单 15-11 中,我将context属性设置为包含文档中h1元素的 jQuery 对象。在complete回调函数中,我使用 jQuery 对象上的css方法(我通过this引用它)来设置所选元素(或元素,因为文档中只有一个)的边框,根据请求的状态改变颜色。您可以在图 15-2 中看到成功和失败请求的结果。
图 15-2 。使用上下文属性来指示 Ajax 请求的结果
提示你可以使用
context属性分配任何对象,因此你有责任确保你用它做适当的事情。例如,如果您将上下文设置为一个HTMLElement对象,那么您必须确保在对其调用任何 jQuery 方法之前将该对象传递给$函数。
使用全局 Ajax 事件
除了我在前一章描述的每个请求的回调函数之外,jQuery 还定义了一组全局事件,您可以使用它们来监控应用发出的所有 Ajax 查询。表 15-6 显示了全球事件可用的方法。
表 15-6 。jQuery Ajax 事件方法
| 方法 | 描述 |
|---|---|
ajaxComplete(function) | 注册 Ajax 请求完成时要调用的函数(不管它是否成功) |
ajaxError(function) | 注册一个在 Ajax 请求遇到错误时调用的函数 |
ajaxSend(function) | 注册一个在 Ajax 请求开始前调用的函数 |
ajaxStart(function) | 注册一个 Ajax 请求启动时要调用的函数 |
ajaxStop(function) | 注册一个在所有 Ajax 请求完成时调用的函数 |
ajaxSuccess(function) | 注册 Ajax 请求成功时要调用的函数 |
提示在 jQuery 1.9 之前,您可以对任何元素调用表中的方法,但是在 jQuery 1.9/2.0 中,您只能对
document元素调用表中的方法,如本节中的示例所示。
这些方法用于注册处理函数,并且必须应用于document元素(正如我稍后演示的)。ajaxStart和ajaxStop方法不向处理函数传递任何参数,但是其他方法提供以下参数:
- 描述事件的
Event对象 - 描述请求的
jqXHR对象 - 包含请求配置的设置对象
ajaxError方法向处理函数传递一个额外的参数,它是对已经发生的错误的描述。
关于这些方法,有两件重要的事情需要记住。第一个是函数将被来自所有 Ajax 请求的事件触发,这意味着你必须小心确保你没有做出只对特定请求成立的假设。
第二件要记住的事情是,在开始发出 Ajax 请求之前,你需要调用这些方法*,以确保处理函数被正确触发。如果在调用ajax方法之后调用全局方法,那么 Ajax 请求可能会在 jQuery 正确注册处理函数之前完成。清单 15-12 展示了如何使用全局 Ajax 事件方法。*
清单 15-12 。使用全局 Ajax 事件方法
...
<script type="text/javascript">
$(document).ready(function () {
$("<div").append("<label>Events:<label>")
.append("<input type='checkbox' id='globalevents' name='globalevents' checked>")
.insertAfter("h1");
$("<ol id='info' class='ajaxinfo'>").insertAfter("h1").append("<li>Ready</li>");
function displayMessage(msg) {
$("#info").append($("<li>").text(msg));
}
$(document)
.ajaxStart(function () {
displayMessage("Ajax Start")
})
.ajaxSend(function (event, jqxhr, settings) {
displayMessage("Ajax Send: " + settings.url)
})
.ajaxSuccess(function (event, jqxhr, settings) {
displayMessage("Ajax Success: " + settings.url)
})
.ajaxError(function (event, jqxhr, settings, errorMsg) {
displayMessage("Ajax Error: " + settings.url)
})
.ajaxComplete(function (event, jqxhr, settings) {
displayMessage("Ajax Complete: " + settings.url)
})
.ajaxStop(function () {
displayMessage("Ajax Stop")
});
$("button").click(function (e) {
$("#row1, #row2, #info").empty();
$.ajax({
url: "mydata.json",
global: $("#globalevents:checked").length > 0,
success: function (data, status, jqxhr) {
var tmplElems = $("#flowerTmpl")
.template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
});
e.preventDefault();
});
});
</script>
...
在清单 15-12 中,我已经为所有的全局 Ajax 事件注册了函数。这些函数调用displayMessage函数 来显示哪个事件被触发了。因为 Ajax 请求可以快速完成,所以我使用一个ol元素来显示到达的消息,建立一个事件列表。
我为button元素的click事件添加了一个处理函数,当按钮被点击时,该函数启动 Ajax 请求。你可以在图 15-3 中看到结果,它显示了点击按钮后 Ajax 请求生成的消息。
图 15-3 。显示全局 Ajax 事件
控制全球事件
您会注意到我在文档中添加了一个复选框。在对ajax函数的调用中,我使用复选框来设置global的值,如清单 15-13 所示。
清单 15-13 。使用全局属性
...
$.ajax({
url: "mydata.json",
global: $("#globalevents:checked").length > 0,
success: function (data, status, jqxhr) {
var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
})
...
当global设置为false时,Ajax 请求不会生成全局 Ajax 事件。您可以使用示例亲自尝试一下。取消选中该框并单击按钮,您将看到 Ajax 请求已经执行,但没有显示任何状态信息。
为 Ajax 请求配置基本设置
有一组设置允许您执行 Ajax 请求的基本配置。这些是现有设置中最没意思的,它们在很大程度上是不言而喻的。表 15-7 描述了这些设置,我将在接下来的章节中演示其中的一小部分。
表 15-7 。基本请求配置设置
| 环境 | 描述 |
|---|---|
accepts | 设置Accept请求头的值,它指定浏览器将接受的 MIME 类型。默认情况下,这由dataType设置决定。 |
cache | 如果设置为false,服务器将不会缓存请求的内容。默认情况下,script和jsonp数据类型不会被缓存,但其他数据类型会被缓存。 |
contentType | 为请求设置Content-Type头。 |
dataType | 指定服务器预期的数据类型。使用此设置时,jQuery 将忽略服务器提供的关于响应类型的信息。参见第十四章了解其工作原理。 |
headers | 指定要添加到请求中的附加标头和值;请参见下面的演示讨论。 |
jsonp | 指定发出 JSONP 请求时代替回调使用的字符串。这需要与服务器协调。有关 JSONP 的详细信息,请参见第十四章。 |
jsonpCallback | 指定回调函数的名称,替换 jQuery 默认使用的随机生成的名称。JSONP 的详细内容见第十四章。 |
password | 指定用于响应身份验证质询的密码。 |
scriptCharset | 当请求 JavaScript 内容时,告诉 jQuery 脚本是用指定的字符集编码的。 |
timeout | 指定请求的超时时间(以毫秒为单位)。如果请求超时,则由error设置指定的功能将被调用,状态为timeout。 |
username | 指定用于响应身份验证质询的用户名。 |
设置超时和标题
用户通常不会意识到 Ajax 请求的发生,所以设置一个超时期限是一个很好的方法,可以避免让用户无所事事地等待一个他们甚至不知道正在发生的进程。清单 15-14 展示了如何为一个请求设置超时。
清单 15-14 。设置超时
...
<script type="text/javascript">
$(document).ready(function() {
$.ajax("mydata.json", {
timeout: 5000,
headers: { "X-HTTP-Method-Override": "PUT" },
success: function(data, status, jqxhr) {
var template = $("#flowerTmpl");
template.tmpl(data.slice(0, 3)).appendTo("#row1");
template.tmpl(data.slice(3)).appendTo("#row2");
},
error: function(jqxhr, status, errorMsg) {
console.log("Error: " + status);
}
});
});
</script>
...
在清单 15-14 中,我使用了timeout设置来指定请求的最大持续时间为五秒。如果请求在这段时间内没有完成,那么将执行由error设置指定的功能,其status值为error。
注意请求一传到浏览器,计时器就开始计时,大多数浏览器对并发请求的数量都有限制。这意味着您冒着在请求开始之前就超时的风险。为了避免这种情况,您必须了解浏览器的局限性以及正在进行的任何其他 Ajax 请求的数量和预期持续时间。
在清单 15-14 中,我还使用了headers设置来给请求添加一个标题,如下所示:
...
headers: { "X-HTTP-Method-Override": "PUT" },
...
使用 map 对象指定附加头。示例中的头是我在上一节“发出 POST 请求”中提到的头。这个头文件对于创建 RESTful web 应用非常有用,只要服务器能够正确理解它。
向服务器发送 JSON 数据
当您需要向服务器发送数据时,可以使用 JSON 格式:这是一种紧凑且富于表现力的数据格式,很容易从 JavaScript 对象中生成。发送 JSON 的过程很简单:只需使用contentType属性来设置请求中的Content-Type头,它告诉服务器正在发送的数据类型,如清单 15-15 中的所示。
清单 15-15 。将 JSON 发送到服务器
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax("mydata.json", {
success: function (data, status, jqxhr) {
var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
}
});
$("button").click(function (e) {
$.ajax({
url: $("form").attr("action"),
contentType: "application/json",
data: JSON.stringify($("form").serializeArray()),
type: "post",
success: processServerResponse
})
e.preventDefault();
})
function processServerResponse(data) {
var inputElems = $("div.dcell").hide();
for (var prop in data) {
var filtered = inputElems.has("input[name=" + prop + "]")
.appendTo("#row1").show();
}
$("#buttonDiv, #totalDiv").remove();
$("#totalTmpl").template(data).appendTo("body");
}
});
</script>
...
我已经使用了contentType设置来指定一个值application/json,这是 JSON 的 MIME 类型。我可以向服务器发送任何对象,但是我想演示如何将表单数据表示为 JSON,如下所示:
...
data: JSON.stringify($("form").serializeArray()),
...
我选择form元素,调用serializeArray方法;这将创建一个对象数组,每个对象都有一个 name 属性和一个 value 属性,表示表单中的一个输入元素。然后我使用JSON.stringify方法将它转换成如下所示的字符串:
[{"name":"aster","value":"1"}, {"name":"daffodil","value":"1"},
{"name":"rose","value":"1"}, {"name":"peony","value":"1"},
{"name":"primula","value":"1"},{"name":"snowdrop","value":"1"}]
所以我有一个 JSON 字符串,它描述了一个我可以发送给服务器的对象数组。我在本章中使用的脚本能够解析和处理这个对象。
使用高级配置设置
在接下来的小节中,我将描述可以应用于 Ajax 请求的最有趣、最有用的高级设置。我发现我并不经常使用它们,但是在需要的时候它们是非常宝贵的,它们提供了对 jQuery 如何处理 Ajax 的细粒度控制。
同步发出请求
async属性指定请求是否将被异步执行。将该属性设置为true(如果未定义该属性,则使用默认值)意味着它将异步执行;值为false意味着请求将被同步执行。
当请求被同步执行时,ajax方法的行为就像一个普通的函数,浏览器将等待请求完成,然后继续执行脚本中的其他语句。清单 15-16 给出了一个例子。
清单 15-16 。发出同步请求
...
<script type="text/javascript">
$(document).ready(function() {
var elems;
$.ajax("flowers.html", {
async: false,
success: function(data, status, jqxhr) {
elems = $(data).filter("div").addClass("dcell");
}
});
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
});
</script>
...
这是我在第十四章中展示的请求,展示了使用 Ajax 时最常见的陷阱,并更新为使用低级 API。这种情况下的不同之处在于,async设置为false,因此浏览器不会执行调用slice和appendTo方法的语句,直到请求完成并且结果被分配给elems变量(假设请求成功完成)。使用 Ajax 方法进行同步调用是一件奇怪的事情,我建议您考虑一下为什么您的 web 应用需要这样做。
我经常在查看麻烦的 Ajax 代码时使用这种技术作为快速测试——不能正确处理异步请求是如此常见的问题,以至于我开始用快速同步测试进行调试。如果代码有效,我知道要开始寻找关于数据何时从服务器到达的错误假设。
提示不要使用同步调用,因为你会发现进行异步调用很费力;我知道使用回调和确保不要对请求的结果做出假设可能会令人厌倦,但是花时间了解这种 web 编程方法确实是值得的。
忽略未修改的数据
只有当响应在您上次查询后发生了变化时,您才可以使用ifModified属性 来接收数据;这由响应中的Last-Modified报头决定。如果您需要重复请求相同的数据来响应用户操作,那么您通常会处理服务器响应并修改文档,以便向用户呈现已经存在的数据。这个设置的默认值是false,它告诉 jQuery 忽略这个头,总是返回数据。清单 15-17 展示了如何使用这个属性。
清单 15-17 。使用 ifModified 属性
...
<script type="text/javascript">
$(document).ready(function () {
$("button").click(function (e) {
$.ajax("mydata.json", {
ifModified: true,
success: function (data, status) {
if (status == "success") {
$("#row1, #row2").children().remove();
var tmplElems = $("#flowerTmpl")
.template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
} else if (status == "notmodified") {
$("img").css("border", "thick solid green");
}
}
});
e.preventDefault();
})
});
</script>
...
在清单 15-17 中,ifModified设置的值是true。总是调用success函数,但是如果自从我最后一次请求以来内容没有被修改,那么data参数将是undefined,而status参数将是notmodified。
在这个例子中,我根据status参数执行不同的操作。如果参数是success,那么我使用data参数向文档中添加元素。如果参数是notmodified,那么我使用css方法为文档中已经存在的img元素添加一个边框。
我调用了ajax方法来响应来自button元素的click事件。这允许我重复提出同样的请求来演示ifModified设置的效果,你可以在图 15-4 中看到。
图 15-4 。使用 ifModified 设置
注意这可能是一个有用的设置,但应该小心使用。如果您的请求是用户操作的结果(比如,按下按钮),那么用户按下按钮可能是因为之前的请求没有按预期的方式执行。假设您请求数据,但是
success方法包含一个错误,不能正确地用内容更新文档;用户按下按钮试图让文档正确显示。通过不明智地使用ifModified设置,您可能会忽略用户操作,迫使用户采取更严肃的步骤来解决问题。
处理响应状态代码
属性 允许您响应 HTTP 响应中返回的不同状态代码。您可以使用这个特性作为success和error属性的替代或补充。清单 15-18 显示了如何单独使用statusCode设置。
清单 15-18 。使用 statusCode 属性
...
<style type="text/css">
.error {color: red; border: medium solid red; padding: 4px;
margin: auto; width: 200px; text-align: center}
</style>
<script type="text/javascript">
$(document).ready(function() {
$.ajax({
url: "mydata.json",
statusCode: {
200: handleSuccessfulRequest,
404: handleFailedRequest,
302: handleRedirect
}
});
function handleSuccessfulRequest(data, status, jqxhr) {
$("#row1, #row2").children().remove();
var template = $("#flowerTmpl");
template.tmpl(data.slice(0, 3)).appendTo("#row1");
template.tmpl(data.slice(3)).appendTo("#row2");
}
function handleRedirect() {
// this function will neber be called
}
function handleFailedRequest(jqxhr, status, errorMsg) {
$("<div class=error>Code: " + jqxhr.status + " Message: "
+ errorMsg + "</div>").insertAfter("h1");
}
});
</script>
...
属性被分配了一个对象,该对象在 HTTP 状态代码和返回到服务器时要执行的函数之间进行映射。在清单 15-18 中,我定义了三个函数,并将它们与状态码200、404和302相关联。
传递给函数的参数取决于状态代码反映的是成功的请求还是错误。如果代码表示成功(比如200,那么参数与success回调函数的参数相同。对于失败状态代码,比如404代码,它表示无法找到请求的文件,参数与error回调函数相同。
注意,我还为302代码添加了一个地图。当服务器希望将您重定向到另一个 URL 时,这将被发送回浏览器。jQuery 会自动跟踪重定向,直到接收到一些内容或遇到错误。这意味着我的302代码的函数永远不会被调用。
提示只有在使用了
ifModified设置的情况下,才会生成304代码,表示内容自上次请求后未被修改。否则,jQuery 会发送一个200代码。有关ifModified设置的信息,参见上一节“忽略未修改的数据”。
当我调试浏览器和服务器之间的交互时,我发现这个特性很有用,通常是为了找出 jQuery 为什么没有按照我想要的方式运行。当我这样做时,我使用statusCode设置来补充success和error设置,并将信息打印到控制台。
提示
success或error回调函数在statusCode设置指定的函数之前执行。
清除响应数据
dataFilter属性 指定了一个函数,这个函数将被调用来处理服务器返回的数据。当服务器发送给您的数据不是您所需要的数据时,这是一个非常有用的功能,因为数据的格式并不完美,或者因为它包含了您不想处理的数据。清单 15-19 展示了dataFilter属性的使用。
清单 15-19 。使用 dataFilter 属性
...
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: "mydata.json",
success: function (data, status, jqxhr) {
$("#row1, #row2").children().remove();
var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
tmplElems.slice(0, 3).appendTo("#row1");
tmplElems.slice(3).appendTo("#row2");
},
dataType: "json",
dataFilter: function (data, dataType) {
if (dataType == "json") {
var filteredData = $.parseJSON(data);
filteredData.shift();
return JSON.stringify(filteredData.reverse());
} else {
return data;
}
}
});
});
</script>
...
向该函数传递从服务器接收的数据和dataType设置的值。如果没有使用dataType设置,那么第二个函数参数将是undefined。dataFilter函数的目的是返回过滤后的数据,在清单 15-19 中,我关注的是json数据类型,如下所示:
...
var filteredData = $.parseJSON(data);
filteredData.shift();
return JSON.stringify(filteredData.reverse());
...
我使用 jQuery parseJSON数据将 JSON 数据转换成 JavaScript 数组(这是我在第三十四章的中描述的 jQuery 实用方法之一)。然后,我使用shift方法移除数组中的第一项,并使用reverse方法颠倒剩余项的顺序。
dataFilter回调函数必须返回一个字符串,所以我调用了JSON.stringify方法,尽管我知道在调用success函数之前,jQuery 会将数据转换回 JavaScript 对象。除此之外,你可以看到我能够从数组中移除一个元素并反转剩余的元素——虽然这不是最有用的转换,但它确实展示了过滤效果,你可以在图 15-5 中看到。
图 15-5 。使用 dataFilter 设置删除项目并反转数据顺序
管理数据转换
我把我最喜欢的一处房产留到了最后。您会注意到,jQuery 在接收某些数据类型时会进行一些方便的转换。例如,当 jQuery 接收到一些 JSON 数据时,它会用一个 JavaScript 对象呈现success函数,而不是原始的 JSON 字符串。
您可以使用converters属性来控制这些转换。此设置的值是一个对象,它在数据类型和用于处理它们的函数之间进行映射。清单 15-20 展示了如何使用该属性将 HTML 数据自动解析成 jQuery 对象。
清单 15-20 。使用转换器设置
...
<script type="text/javascript">
$(document).ready(function() {
$.ajax({
url: "flowers.html",
success: function(data, status, jqxhr) {
var elems = data.filter("div").addClass("dcell");
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
},
converters: {
"text html": function(data) {
return $(data);
}
}
});
});
</script>
...
我为text html类型注册了一个函数。注意,您在 MIME 类型的组件之间使用了一个空格(与text/html相反)。向该函数传递从服务器接收的数据,并返回转换后的数据。在本例中,我将从flowers.html文件获得的 HTML 片段传递给 jQuery $函数,并返回结果。这意味着我可以在作为数据参数传递给success函数的对象上调用所有常用的 jQuery 方法。
提示数据类型并不总是与服务器返回的 MIME 类型匹配。例如,
application/json通常在converters方法中表示为"text json"。
很容易被这些转换器冲昏头脑。我尽量避免在这些功能上做过多的工作。例如,我有时很想获取 JSON 数据,应用数据模板,并将结果 HTML 元素传回。虽然这是一个很好的技巧,但是如果其他人试图扩展您的代码,或者您需要放松繁重的处理以在以后获得原始数据,它会使您出错。
设置和过滤 Ajax 请求
在本章的最后,我将描述 jQuery 提供的一些额外的方法来简化请求的配置。
定义默认设置
ajaxSetup方法 指定了将用于每个 Ajax 请求的设置,使您不必为每个请求定义您感兴趣的所有设置。清单 15-21 展示了这种方法的使用。
清单 15-21 。使用 ajaxSetup 方法
...
<script type="text/javascript">
$(document).ready(function() {
$.ajaxSetup({
timeout: 15000,
global: false,
error: function(jqxhr, status, errorMsg) {
$("<div class=error/>")
.text("Status: " + status + " Error: " + errorMsg)
.insertAfter("h1");
},
converters: {
"text html": function(data) {
return $(data);
}
}
});
$.ajax({
url: "flowers.html",
success: function(data, status, jqxhr) {
var elems = data.filter("div").addClass("dcell");
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
},
});
});
</script>
...
对 jQuery $函数调用ajaxSetup方法,就像对ajax方法一样。ajaxSetup的参数是一个对象,它包含了所有 Ajax 请求的默认设置。在清单 15-21 中,我定义了timeout、global、error和converters设置的默认值。一旦我调用了ajaxSetup方法,我只需要为那些我没有提供默认值或者我想改变其值的设置定义值。当发出大量具有相似配置的 Ajax 请求时,这可以减少代码重复。
提示
ajaxSetup方法指定的设置也会影响我在第十四章中展示的方便快捷的方法提出的请求。这是一种将低级 API 附带的详细控制与便利方法的简单性结合起来的好方法。
过滤请求
如果您想为单个请求动态定制设置,可以使用ajaxPrefilter方法 ,如清单 15-22 中的所示。
清单 15-22 。使用 ajaxPrefilter 方法
...
<script type="text/javascript">
$(document).ready(function () {
$.ajaxSetup({
timeout: 15000,
global: false,
error: function (jqxhr, status, errorMsg) {
$("<div class=error/>")
.text("Status: " + status + " Error: " + errorMsg)
.insertAfter("h1");
},
converters: {
"text html": function (data) {
return $(data);
}
}
});
$.ajaxPrefilter("json html", function (settings, originalSettings, jqxhr) {
if (originalSettings.dataType == "html") {
settings.timeout = 2000;
} else {
jqxhr.abort();
}
});
$.ajax({
url: "flowers.html",
success: function (data, status, jqxhr) {
var elems = data.filter("div").addClass("dcell");
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
},
});
});
</script>
...
ajaxPrefilter方法的参数是一组数据类型和一个回调函数,当发出对这些数据类型的请求时,将执行该函数。如果省略数据类型,只指定函数,那么所有请求都会调用该函数。
传递给回调函数的参数是请求的设置(包括使用ajaxSetup方法设置的任何默认值);传递给 Ajax 方法(不包括任何默认值)和请求的jqXHR对象的原始设置。对作为第一个参数传递的对象进行更改,如示例所示。
在清单 15-22 中,我过滤了 JSON 和 HTML 请求,因此如果在传递给 Ajax 方法的设置中指定了dataType设置,我将超时设置为两秒。对于没有这个设置的请求,我调用了jqXHR对象上的abort方法来阻止请求被发送。
摘要
在这一章中,我向你展示了低级的 jQuery Ajax 接口,我希望你同意,它并不比我在第十四章第一部分中展示的方便快捷的方法难多少。只需做一点额外的工作,您就可以控制 Ajax 请求处理方式的许多方面,这为您提供了无数种方式来根据您的需要和偏好调整处理过程。在第十六章中,我重构了这个例子,加入了我在这本书的这一部分描述的特性和技术。
十六、重构例子:第二部分
我在本书的这一部分介绍了一些丰富的特性,正如我在之前的第十一章中所做的那样,我想把它们放在一起,以给出 jQuery 的一个更广阔的视角。
提示我不打算在这一章中试图保留一个可行的非 JavaScript 结构,因为我添加到示例中的所有特性都严重依赖于 JavaScript。
查看重构的示例
在第十一章中,我使用核心 jQuery 特性来重构示例,以包括 DOM(域对象模型)操作、效果和事件。清单 16-1 显示了我最终得到的文档,它将是本章的起点,因为我整合了本书这一部分的特性。
清单 16-1 。本章的起点
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<style type="text/css">
a.arrowButton {
background-image: url(leftarrows.png); float: left;
margin-top: 15px; display: block; width: 50px; height: 50px;
}
#right {background-image: url(rightarrows.png)}
h1 { min-width: 0px; width: 95%; }
#oblock { float: left; display: inline; border: thin black solid; }
form { margin-left: auto; margin-right: auto; width: 885px; }
#bbox {clear: left}
</style>
<script type="text/javascript">
$(document).ready(function() {
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({name: fNames[i],
value: 0, required: "required"})
}
$("<a id=left></a><a id=right></a>").prependTo("form")
.addClass("arrowButton").click(handleArrowPress).hover(handleArrowMouse);
$("#right").appendTo("form");
$("#row2, #row3").hide();
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({clear: "both", padding: "5px"});
$("<div id=bbox />").appendTo("body").append(total);
$("input").change(function(e) {
var total = 0;
$("input").each(function(index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
});
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex = jQuery.inArray(visibleRow.attr("id"),elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) {targetRowIndex = elemSequence.length -1};
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function() {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")});
}
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post" action="http://node.jacquisflowershop.com/order">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow">
<div class="dcell">
<img src="aster.png"/><label for="aster">Aster:</label>
<input name="aster" value="0" />
</div>
<div class="dcell">
<img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
<input name="daffodil" value="0"/>
</div>
<div class="dcell">
<img src="rose.png"/><label for="rose">Rose:</label>
<input name="rose" value="0" />
</div>
</div>
<div id="row2"class="drow">
<div class="dcell">
<img src="peony.png"/><label for="peony">Peony:</label>
<input name="peony" value="0" />
</div>
<div class="dcell">
<img src="primula.png"/><label for="primula">Primula:</label>
<input name="primula" value="0" />
</div>
<div class="dcell">
<img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
<input name="snowdrop" value="0" />
</div>
</div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
提示我在脚本中的很多地方动态地插入了元素。我不会让它们保持静态,而是让它们保持原样,这样我就可以专注于添加新功能。
这不是完全相同的文档:我通过添加一个style元素,而不是在单个选择上使用css方法,整理了级联样式表(CSS ) 的附加内容。你可以在图 16-1 中看到这个 HTML 文档是如何出现在浏览器中的,当然,第十一章分解了到目前为止我对文档所做的一系列修改。
图 16-1 。本章中示例文档的起点
更新 Node.js 脚本
我需要为本章升级formserver.js服务器端脚本。清单 16-2 中的所示的变化是为了丰富提交表单时返回的数据,并支持新的验证特性。与本书中的所有示例一样,您可以从 Apress 网站(www.apress.com)的源代码/下载区下载修改后的formserver.js文件。
清单 16-2 。修改后的 Node.js 脚本
var http = require("http");
var querystring = require("querystring");
var url = require("url");
var port = 80;
http.createServer(function (req, res) {
console.log("[200 OK] " + req.method + " to " + req.url);
if (req.method == "OPTIONS") {
res.writeHead(200, "OK", {
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Origin": "http://www.jacquisflowershop.com"
});
res.end();
} else if (req.method == "POST") {
var dataObj = new Object();
var contentType = req.headers["content-type"];
var fullBody = "";
if (contentType) {
if (contentType.indexOf("application/x-www-form-urlencoded") > -1) {
req.on("data", function (chunk) { fullBody += chunk.toString(); });
req.on("end", function () {
var dBody = querystring.parse(fullBody);
writeResponse(req, res, dBody,
url.parse(req.url, true).query["callback"])
});
} else {
req.on("data", function (chunk) { fullBody += chunk.toString(); });
req.on("end", function () {
dataObj = JSON.parse(fullBody);
var dprops = new Object();
for (var i = 0; i < dataObj.length; i++) {
dprops[dataObj[i].name] = dataObj[i].value;
}
writeResponse(req, res, dprops);
});
}
}
} else if (req.method == "GET") {
var data = url.parse(req.url, true).query;
writeResponse(req, res, data, data["callback"])
}
var flowerData = {
aster: { price: 2.99, stock: 10, plural: "Asters" },
daffodil: { price: 1.99, stock: 10, plural: "Daffodils" },
rose: { price: 4.99, stock: 2, plural: "Roses" },
peony: { price: 1.50, stock: 3, plural: "Peonies" },
primula: { price: 3.12, stock: 20, plural: "Primulas" },
snowdrop: { price: 0.99, stock: 5, plural: "Snowdrops" },
carnation: { price: 0.50, stock: 1, plural: "Carnations" },
lily: { price: 1.20, stock: 2, plural: "Lillies" },
orchid: { price: 10.99, stock: 5, plural: "Orchids" }
}
function writeResponse(req, res, data, jsonp) {
var jsonData;
if (req.url == "/stockcheck") {
for (flower in data) {
if (flowerData[flower].stock >= data[flower]) {
jsonData = true;
} else {
jsonData = "We only have " + flowerData[flower].stock + " "
+ flowerData[flower].plural + " in stock";
}
break;
}
jsonData = JSON.stringify(jsonData);
} else {
var totalCount = 0;
var totalPrice = 0;
for (item in data) {
if (item != "_"&&data[item] > 0) {
var itemNum = Number(data[item])
totalCount += itemNum;
totalPrice += (itemNum * flowerData[item].price);
} else {
delete data[item];
}
}
data.totalItems = totalCount;
data.totalPrice = totalPrice.toFixed(2);
jsonData = JSON.stringify(data);
if (jsonp) {
jsonData = jsonp + "(" + jsonData + ")";
}
}
res.writeHead(200, "OK", {
"Content-Type": jsonp ? "text/javascript" : "application/json",
"Access-Control-Allow-Origin": "*"
});
res.write(jsonData);
res.end();
}
}).listen(port);
console.log("Ready on port " + port);
对浏览器的响应现在包括使用form元素选择并提交给服务器的商品的总价格,返回如下所示的 JSON 结果:
{"aster":"1","daffodil":"2","rose":"4","totalItems":7,"totalPrice":"26.93"}
我通过在命令提示符下输入以下命令来运行该脚本:
node.exe formserver.js
准备 Ajax
首先,我将添加一些基本元素和样式,用于显示 Ajax 请求错误,并设置适用于所有 Ajax 请求的基本配置。清单 16-3 显示了对文档的修改。
清单 16-3 。设置对 Ajax 请求和错误处理的支持
...
<style type="text/css">
a.arrowButton {
background-image: url(leftarrows.png); float: left;
margin-top: 15px; display: block; width: 50px; height: 50px;
}
#right {background-image: url(rightarrows.png)}
h1 { min-width: 0px; width: 95%; }
#oblock { float: left; display: inline; border: thin black solid; }
form { margin-left: auto; margin-right: auto; width: 885px; }
#bbox {clear: left}
#error {color: red; border: medium solid red; padding: 4px; margin: auto;
width: 300px; text-align: center; margin-bottom: 5px}
</style>
<script type="text/javascript">
$(document).ready(function () {
$.ajaxSetup({
timeout: 5000,
converters: {"text html": function (data) { return $(data); }}
});
$(document).ajaxError(function (e, jqxhr, settings, errorMsg) {
$("#error").remove();
var msg = "An error occurred. Please try again"
if (errorMsg == "timeout") {
msg = "The request timed out. Please try again"
} else if (jqxhr.status == 404) {
msg = "The file could not be found";
}
$("<div id=error/>").text(msg).insertAfter("h1");
}).ajaxSuccess(function () {
$("#error").remove();
});
var fNames = ["Carnation", "Lily", "Orchid"];
var fRow = $("<div id=row3 class=drow/>").appendTo("div.dtable");
var fTemplate = $("<div class=dcell><img/><label/><input/></div>");
for (var i = 0; i < fNames.length; i++) {
fTemplate.clone().appendTo(fRow).children()
.filter("img").attr("src", fNames[i] + ".png").end()
.filter("label").attr("for", fNames[i]).text(fNames[i]).end()
.filter("input").attr({
name: fNames[i], value: 0, required: "required"
})
};
$("<a id=left></a><a id=right></a>").prependTo("form")
.addClass("arrowButton").click(handleArrowPress).hover(handleArrowMouse);
$("#right").appendTo("form");
$("#row2, #row3").hide();
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({ clear: "both", padding: "5px" });
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
$("input").change(function (e) {
var total = 0;
$("input").each(function (index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
});
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex =
jQuery.inArray(visibleRow.attr("id"), elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) { targetRowIndex = elemSequence.length - 1 };
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function () {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")
});
}
});
</script>
...
我使用了全局 Ajax 事件来设置一个简单的错误显示。当出现错误时,会创建新元素来描述问题。我展示的简单错误消息来自我从 jQuery 获得的信息,但是在实际的 web 应用中,这些消息应该更具描述性,并提供解决方法的建议。你可以在图 16-2 的中看到一个显示给用户的错误示例。
图 16-2 。显示 Ajax 的错误消息
该错误会一直显示,直到发出一个成功的请求或出现另一个错误,此时元素将从文档中删除。除了全局事件,我还使用了ajaxSetup方法来定义timeout设置的值,并为 HTML 片段提供一个转换器,以便 jQuery 自动处理它们。
获取产品信息
下一个变化是删除现有的产品元素和向列表中添加三朵额外花朵的循环,用一对 Ajax 调用和一个数据模板替换它们。然而,首先,我创建了一个名为additionalflowers.json的文件,并将它放在与其他示例文件相同的目录中。清单 16-4 显示了additionalflowers.json文件的内容。
清单 16-4 。Additionalflowers.json 文件的内容
[{"name":"Carnation","product":"carnation"},
{"name":"Lily","product":"lily"},
{"name":"Orchid","product":"orchid"}]
这个文件包含我想展示的附加产品的 JSON 描述。我将获得 HTML 片段形式的主要产品集,然后通过处理 JSON 数据添加到该集中。清单 16-5 显示了这些变化。
清单 16-5 。通过 HTML 设置产品并通过 Ajax 获得 JSON
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<style type="text/css">
a.arrowButton {
background-image: url(leftarrows.png); float: left;
margin-top: 15px; display: block; width: 50px; height: 50px;
}
#right {background-image: url(rightarrows.png)}
h1 { min-width: 0px; width: 95%; }
#oblock { float: left; display: inline; border: thin black solid; }
form { margin-left: auto; margin-right: auto; width: 885px; }
#bbox {clear: left}
#error {color: red; border: medium solid red; padding: 4px; margin: auto;
width: 300px; text-align: center; margin-bottom: 5px}
</style>
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" value="0" />
</div>
{{/flowers}}
</script>
<script type="text/javascript">
$(document).ready(function () {
$.ajaxSetup({
timeout: 5000,
converters: {"text html": function(data) { return $(data); }}
});
$(document).ajaxError(function (e, jqxhr, settings, errorMsg) {
$("#error").remove();
var msg = "An error occurred. Please try again"
if (errorMsg == "timeout") {
msg = "The request timed out. Please try again"
} else if (jqxhr.status == 404) {
msg = "The file could not be found";
}
$("<div id=error/>").text(msg).insertAfter("h1");
}).ajaxSuccess(function () {
$("#error").remove();
});
$("<a id=left></a><a id=right></a>").prependTo("form")
.addClass("arrowButton").click(handleArrowPress).hover(handleArrowMouse);
$("#right").appendTo("form");
$("#row2, #row3").hide();
$.get("flowers.html", function (data) {
var elems = data.filter("div").addClass("dcell");
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
})
$.getJSON("additionalflowers.json", function (data) {
$("#flowerTmpl").template({ flowers: data }).appendTo("#row3");
})
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({ clear: "both", padding: "5px" });
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
$("input").change(function (e) {
var total = 0;
$("input").each(function (index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
});
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex =
jQuery.inArray(visibleRow.attr("id"), elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) { targetRowIndex = elemSequence.length - 1 };
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function () {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")
});
}
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post" action="[`node.jacquisflowershop.com/order`](http://node.jacquisflowershop.com/order)">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow"></div>
<div id="row2" class="drow"></div>
<div id="row3" class="drow"></div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
我使用了 Ajax 速记方法来获取创建行所需的 HTML 片段和 JSON 数据。从脚本中可能看不出来,但是速记方法的一个好处是它们只是对低级 API 调用的包装——这意味着您通过ajaxSetup方法应用的设置就像您直接使用ajax方法一样工作。除了对get和getJSON方法的调用之外,我还添加了一个数据模板,这样我就可以轻松地处理 JSON。文档的外观没有变化,但是内容的来源发生了变化。
添加表单验证
下一步是向input元素添加验证。清单 16-6 显示了需要添加的内容。
清单 16-6 。添加表单验证
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<script src="jquery.validate.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<style type="text/css">
a.arrowButton {
background-image: url(leftarrows.png); float: left;
margin-top: 15px; display: block; width: 50px; height: 50px;
}
#right {background-image: url(rightarrows.png)}
h1 { min-width: 0px; width: 95%; }
#oblock { float: left; display: inline; border: thin black solid; }
form { margin-left: auto; margin-right: auto; width: 885px; }
#bbox {clear: left}
#error {color: red; border: medium solid red; padding: 4px; margin: auto;
width: 300px; text-align: center; margin-bottom: 5px}
.invalidElem {border: medium solid red}
#errorSummary {border: thick solid red; color: red; width: 350px; margin: auto;
padding: 4px; margin-bottom: 5px}
</style>
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" value="0" />
</div>
{{/flowers}}
</script>
<script type="text/javascript">
$(document).ready(function () {
$.ajaxSetup({
timeout: 5000,
converters: {"text html": function(data) { return $(data); }}
});
$(document).ajaxError(function (e, jqxhr, settings, errorMsg) {
$("#error").remove();
var msg = "An error occurred. Please try again"
if (errorMsg == "timeout") {
msg = "The request timed out. Please try again"
} else if (jqxhr.status == 404) {
msg = "The file could not be found";
}
$("<div id=error/>").text(msg).insertAfter("h1");
}).ajaxSuccess(function () {
$("#error").remove();
});
$("<a id=left></a><a id=right></a>").prependTo("form")
.addClass("arrowButton").click(handleArrowPress).hover(handleArrowMouse);
$("#right").appendTo("form");
$("#row2, #row3").hide();
var flowerReq =$.get("flowers.html", function (data) {
var elems = data.filter("div").addClass("dcell");
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
});
var jsonReq =$.getJSON("additionalflowers.json", function (data) {
$("#flowerTmpl").template({ flowers: data }).appendTo("#row3");
});
$("<div id=errorSummary>").text("Please correct the following errors:")
.append("<ul id='errorsList'></ul>").hide().insertAfter("h1");
$("form").validate({
highlight: function(element, errorClass) {
$(element).addClass("invalidElem");
},
unhighlight: function(element, errorClass) {
$(element).removeClass("invalidElem");
},
errorContainer: "#errorSummary",
errorLabelContainer: "#errorsList",
wrapper: "li",
errorElement: "div"
});
var plurals = {
aster: "Asters", daffodil: "Daffodils", rose: "Roses",
peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops",
carnation: "Carnations", lily: "Lillies", orchid: "Orchids"
};
$.when(flowerReq, jsonReq).then(function() {
$("input").each(function(index, elem) {
$(elem).rules("add", {
required: true,
min: 0,
digits: true,
messages: {
required: "Please enter a number of " + plurals[elem.name],
digits: "Please enter a number of" + plurals[elem.name],
min: "Please enter a positivenumber of "
+ plurals[elem.name]
}
})
}).change(function(e) {
if ($("form").validate().element($(e.target))) {
var total = 0;
$("input").each(function(index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
}
});
});
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({ clear: "both", padding: "5px" });
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
$("input").change(function (e) {
var total = 0;
$("input").each(function (index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
});
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex =
jQuery.inArray(visibleRow.attr("id"), elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) { targetRowIndex = elemSequence.length - 1 };
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function () {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")
});
}
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post" action="http://node.jacquisflowershop.com/order">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow"></div>
<div id="row2" class="drow"></div>
<div id="row3" class="drow"></div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
在这个清单中,我导入了验证插件,并定义了一些用于样式化验证错误的 CSS。我调用form元素上的validate方法来设置表单验证,指定一个验证摘要,就像我在第十三章中所做的一样。
我使用 Ajax 为花卉产品生成元素的事实给了我一个需要解决的问题。当然,这些是异步调用,所以我不能假设 Ajax 调用后的语句中文档中是否存在input元素。这是我在第十四章中描述的常见陷阱,如果浏览器在两个 Ajax 请求完成之前执行我对input元素的选择,我将不会匹配任何元素(因为它们还没有被创建和添加到文档中),我的验证设置将会失败。
为了解决这个问题,我使用了when和then方法,它们是我在第三十五章中描述的 jQuery 延迟对象特性的一部分。以下是相关声明:
...
$.when(flowerReq, jsonReq).then(function() {
$("input").each(function(index, elem) {
$(elem).rules("add", {
required: true,
min: 0,
digits: true,
messages: {
required: "Please enter a number of " + plurals[elem.name],
digits: "Please enter a number of" + plurals[elem.name],
min: "Please enter a positive number of "
+ plurals[elem.name]
}
})
}).change(function(e) {
if ($("form").validate().element($(e.target))) {
var total = 0;
$("input").each(function(index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
}
});
})
...
我不想超越自己,但是所有 Ajax 方法返回的jqXHR对象可以作为参数传递给when方法,如果两个请求都成功,传递给then方法的函数将被执行。
我在传递给then方法的函数中设置了表单验证,选择了input元素并为每个元素添加了我需要的验证规则。我已经指定了值是必需的,它们必须是数字,并且最小可接受值是零。我为每个验证检查定义了自定义消息,这些消息引用了多个花名的数组来帮助它们对用户有意义。
因为我选择了input元素,所以我借此机会为change事件提供了一个处理函数,当输入到字段中的值发生变化时就会触发这个函数。注意,我调用了element方法,如下所示:
...
if ($("form").validate().element($(e.target))) {
...
这将触发对已更改元素的验证,该方法的结果是一个布尔值,表明输入值的有效性。通过使用一个if块,我避免了将无效值添加到我所选择的项目的运行总数中。
添加远程验证
我在清单 16-6 中执行的验证和我在第十三章中描述的验证都是本地验证的例子,也就是说规则和执行规则所需的数据都是 HTML 文档的一部分。
验证插件还支持远程验证,用户输入的值被发送到服务器,规则在那里被应用。当您不想将验证规则发送到浏览器时,这很有用,因为这需要太多的数据(例如,您可以通过检查用户名是否已被使用来验证新帐户的用户名,这需要将所有帐户名发送到客户端进行本地验证)。
注意使用远程验证时需要注意,因为它会给服务器带来很大的负载。在这个例子中,每当用户改变一个
input元素的值时,我就执行一次远程验证,这在实际应用中可能会产生很多请求。更明智的方法通常是在提交表单之前进行远程验证。
我没有在第十三章中解释远程验证,因为它依赖于 JSON 和 Ajax,我不想太早进入这些话题。清单 16-7 展示了我如何设置远程验证,我用它来确保用户订购的商品不会超过库存。
清单 16-7 。执行远程验证
...
$.when(flowerReq, jsonReq).then(function() {
$("input").each(function(index, elem) {
$(elem).rules("add", {
required: true,
min: 0,
digits: true,
remote: {
url: "[`node.jacquisflowershop.com/stockcheck`](http://node.jacquisflowershop.com/stockcheck)",
type: "post",
global: false
},
messages: {
required: "Please enter a number of " + plurals[elem.name],
digits: "Please enter a number of" + plurals[elem.name],
min: "Please enter a positive number of "
+ plurals[elem.name]
}
})
}).change(function(e) {
if ($("form").validate().element($(e.target))) {
var total = 0;
$("input").each(function(index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
}
});
});
...
设置远程验证很容易:我通过将remote属性设置为 map 对象来指定验证检查,该对象配置验证插件将向用户发出的 Ajax 请求。在这个例子中,我使用了url设置来指定将被调用来执行远程验证的 URL,使用了type设置来指定我想要一个POST请求,使用了global设置来禁用全局事件。
我禁用了全局事件,因为我不希望在发出远程验证 Ajax 请求时出现的错误被视为用户可以处理的一般错误。相反,我希望它们悄悄地失败,因为当表单被提交时,服务器将执行进一步的验证(正如我在第十三章中解释的那样,formserver.js脚本不执行任何验证,但是真正的 web 应用执行验证是很重要的)。
验证插件使用标准的 jQuery Ajax 设置向指定的远程验证 URL 发出请求,发送input元素的name和用户输入的值。如果我在 Aster input元素中输入22,然后离开以触发change事件,验证插件将向服务器发出 HTTP POST 请求,其中包含以下信息:
aster=22
服务器发送的响应很简单。如果响应是单词true,则该值有效。任何其他响应都被视为将向用户显示的错误消息。我的formserver.js脚本将发回如下错误消息:
We only have 10 Asters in stock
该消息被视为本地验证消息,如图图 16-3 所示。
图 16-3 。显示远程验证消息
使用 Ajax 提交表单数据
在表单中提交值非常简单,清单 16-8 中的展示了我在第十五章中使用的技术。
清单 16-8 。使用 Ajax 提交表单
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<script src="jquery.validate.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<style type="text/css">
a.arrowButton {
background-image: url(leftarrows.png); float: left;
margin-top: 15px; display: block; width: 50px; height: 50px;
}
#right {background-image: url(rightarrows.png)}
h1 { min-width: 0px; width: 95%; }
#oblock { float: left; display: inline; border: thin black solid; }
form { margin-left: auto; margin-right: auto; width: 885px; }
#bbox {clear: left}
#error {color: red; border: medium solid red; padding: 4px; margin: auto;
width: 300px; text-align: center; margin-bottom: 5px}
.invalidElem {border: medium solid red}
#errorSummary {border: thick solid red; color: red; width: 350px; margin: auto;
padding: 4px; margin-bottom: 5px}
#popup {
text-align: center; position: absolute; top: 100px;
left: 0px; width: 100%; height: 1px; overflow: visible; visibility: visible;
display: block }
#popupContent { color: white; background-color: black; font-size: 14px;
font-weight: bold; margin-left: -75px; position: absolute; top: -55px;
left: 50%; width: 150px; height: 60px; padding-top: 10px; z-index: 2; }
</style>
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" value="0" />
</div>
{{/flowers}}
</script>
<script type="text/javascript">
$(document).ready(function () {
$("<div id='popup'><div id='popupContent'><img src='progress.gif'"
+ "alt='progress'/><div>Placing Order</div></div></div>")
.appendTo("body");
$.ajaxSetup({
timeout: 5000,
converters: {"text html": function (data) { return $(data); }}
});
$(document).ajaxError(function (e, jqxhr, settings, errorMsg) {
$("#error").remove();
var msg = "An error occurred. Please try again"
if (errorMsg == "timeout") {
msg = "The request timed out. Please try again"
} else if (jqxhr.status == 404) {
msg = "The file could not be found";
}
$("<div id=error/>").text(msg).insertAfter("h1");
}).ajaxSuccess(function () {
$("#error").remove();
});
$("<a id=left></a><a id=right></a>").prependTo("form")
.addClass("arrowButton").click(handleArrowPress).hover(handleArrowMouse);
$("#right").appendTo("form");
$("#row2, #row3,#popup").hide();
var flowerReq = $.get("flowers.html", function (data) {
var elems = data.filter("div").addClass("dcell");
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
});
var jsonReq = $.getJSON("additionalflowers.json", function (data) {
$("#flowerTmpl").template({ flowers: data }).appendTo("#row3");
});
$("<div id=errorSummary>").text("Please correct the following errors:")
.append("<ul id='errorsList'></ul>").hide().insertAfter("h1");
$("form").validate({
highlight: function(element, errorClass) {
$(element).addClass("invalidElem");
},
unhighlight: function(element, errorClass) {
$(element).removeClass("invalidElem");
},
errorContainer: "#errorSummary",
errorLabelContainer: "#errorsList",
wrapper: "li",
errorElement: "div"
});
var plurals = {
aster: "Asters", daffodil: "Daffodils", rose: "Roses",
peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops",
carnation: "Carnations", lily: "Lillies", orchid: "Orchids"
};
$.when(flowerReq, jsonReq).then(function() {
$("input").each(function(index, elem) {
$(elem).rules("add", {
required: true,
min: 0,
digits: true,
remote: {
url: "http://node.jacquisflowershop.com/stockcheck",
type: "post",
global: false
},
messages: {
required: "Please enter a number of " + plurals[elem.name],
digits: "Please enter a number of" + plurals[elem.name],
min: "Please enter a positive number of "
+ plurals[elem.name]
}
})
}).change(function(e) {
if ($("form").validate().element($(e.target))) {
var total = 0;
$("input").each(function(index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
}
});
});
$("button").click(function (e) {
e.preventDefault();
var formData = $("form").serialize();
$("body *").not("#popup, #popup *").css("opacity", 0.5);
$("input").attr("disabled", "disabled");
$("#popup").show();
$.ajax({
url: "[`node.jacquisflowershop.com/order`](http://node.jacquisflowershop.com/order)",
type: "post",
data: formData,
complete: function () {
setTimeout(function () {
$("body *").not("#popup, #popup *").css("opacity", 1);
$("input").removeAttr("disabled");
$("#popup").hide();
}, 1500);
}
})
});
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({ clear: "both", padding: "5px" });
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
$("input").change(function (e) {
var total = 0;
$("input").each(function (index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
});
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex =
jQuery.inArray(visibleRow.attr("id"), elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) { targetRowIndex = elemSequence.length - 1 };
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function () {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")
});
}
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form method="post" action="http://node.jacquisflowershop.com/order">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow"></div>
<div id="row2" class="drow"></div>
<div id="row3" class="drow"></div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
</body>
</html>
我不仅仅是发出一个 Ajax POST 请求,因为我想提供一些额外的上下文来说明在实际项目中如何处理这些请求。首先,我添加了一个元素,它位于文档中所有其他元素的上方,并告诉用户订单已经发出。下面是创建这些元素的语句:
...
$("<div id='popup'><div id='popupContent'><img src='progress.gif'"
+ "alt='progress'/><div>Placing Order</div></div></div>").appendTo("body");
...
我还为这些新元素在style元素中添加了一些 CSS。
...
#popup {
text-align: center; position: absolute; top: 100px;
left: 0px; width: 100%; height: 1px; overflow: visible; visibility: visible;
display: block }
#popupContent { color: white; background-color: black; font-size: 14px;
font-weight: bold; margin-left: -75px; position: absolute; top: -55px;
left: 50%; width: 150px; height: 60px; padding-top: 10px; z-index: 2; }
...
很难创建一个看起来像弹出窗口并在屏幕上正确定位的元素,并且您可以看到使它工作所需的 CSS 量是相当大的。相比之下,HTML 元素本身很简单,从 jQuery 语句生成的 HTML 如下所示:
...
<div id="popup" style="display: none;">
<div id="popupContent">
<img src="progress.gif" alt="progress">
<div>Placing Order</div>
</div>
</div>
...
我指定的img元素(progress.gif)是一个动画 GIF 图像。有许多网站可以根据您的需求生成进度图像,我使用了其中的一个。如果您不想创建自己的代码,那么可以使用本示例中的代码,它包含在本书的源代码/下载区域中(可以从 Apress 网站[www.apress.com]??]免费获得)。你可以看到进度元素是如何出现在图 16-4 中的(为了清晰起见,我已经删除了其他元素)。
图 16-4 。向用户显示进度
我最初隐藏了这些元素,因为在用户真正下订单之前,向用户显示进度是没有意义的。
...
$("#row2, #row3,#popup").hide();
...
这些元素就位并隐藏后,我转向表单提交。我为button元素的click事件注册了一个处理函数,如下所示:
...
$("button").click(function (e) {
e.preventDefault();
var formData = $("form").serialize();
$("body *").not("#popup, #popup *").css("opacity", 0.5);
$("input").attr("disabled", "disabled");
$("#popup").show();
$.ajax({
url: "http://node.jacquisflowershop.com/order",
type: "post",
data: formData,
complete: function () {
setTimeout(function () {
$("body *").not("#popup, #popup *").css("opacity", 1);
$("input").removeAttr("disabled");
$("#popup").hide();
}, 1500);
}
})
});
...
在启动 Ajax 请求之前,我展示了弹出元素,并使所有其他元素部分透明。我通过添加disabled属性禁用了input元素,因为我不希望在我向用户发送数据时,用户能够更改任何input元素的值。
...
$("body *").not("#popup, #popup *").css("opacity", 0.5);
$("input").attr("disabled", "disabled");
$("#popup').show();
...
禁用input元素的问题是它们的值不会包含在发送到服务器的数据中。按照 HTML 规范的定义,serialize方法将只包括来自被认为是成功控件的input元素的值;这排除了那些被禁用或者没有name属性的元素。我可以自己遍历input元素并获取值,但是在禁用元素之前收集要发送的数据更简单,如下所示:
...
var formData = $("form").serialize();
...
我使用了complete设置,通过使所有元素不透明,从input元素中移除disabled属性,并隐藏弹出元素,将界面恢复到正常状态。在恢复接口之前,我在请求完成之后人为地引入了 1.5 秒的延迟,如下所示:
...
complete: function() {
setTimeout(function() {
$("body *").not("#popup, #popup *").css("opacity", 1);
$("input").removeAttr("disabled");
$("#popup").hide();
}, 1500);
}
...
我不会在真实的 web 应用中这样做,但是出于演示的目的,当开发机器和服务器在同一个本地网络上时,这对于强调转换是有用的。你可以在图 16-5 的中看到 Ajax 请求期间浏览器是如何出现的。
图 16-5 。表单提交期间的浏览器请求
处理服务器响应
剩下的就是对我从服务器上获得的数据做一些有用的事情。对于这一章,我将使用一个简单的表格。在本书的下一部分中,您将学习如何使用 jQuery UI 创建丰富的用户界面,我不想手工完成我可以用 UI 小部件更优雅地完成的工作。图 16-6 显示了最终的结果。
图 16-6 。显示订单摘要
清单 16-9 显示了支持这一增强的 HTML 文档的变化。在接下来的部分中,我将一步一步地分解我所做的更改。
清单 16-9 。处理来自服务器的响应
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<script src="jquery.validate.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css"/>
<style type="text/css">
a.arrowButton {
background-image: url(leftarrows.png); float: left;
margin-top: 15px; display: block; width: 50px; height: 50px;
}
#right {background-image: url(rightarrows.png)}
h1 { min-width: 0px; width: 95%; }
#oblock { float: left; display: inline; border: thin black solid; }
#orderForm { margin-left: auto; margin-right: auto; width: 885px; }
#bbox {clear: left}
#error {color: red; border: medium solid red; padding: 4px; margin: auto;
width: 300px; text-align: center; margin-bottom: 5px}
.invalidElem {border: medium solid red}
#errorSummary {border: thick solid red; color: red; width: 350px; margin: auto;
padding: 4px; margin-bottom: 5px}
#popup {
text-align: center; position: absolute; top: 100px;
left: 0px; width: 100%; height: 1px; overflow: visible; visibility: visible;
display: block }
#popupContent { color: white; background-color: black; font-size: 14px;
font-weight: bold; margin-left: -75px; position: absolute; top: -55px;
left: 50%; width: 150px; height: 60px; padding-top: 10px; z-index: 2; }
#summary {text-align: center}
table {border-collapse: collapse; border: medium solid black; font-size: 18px;
margin: auto; margin-bottom: 5px;}
th {text-align: left}
th, td {padding: 2px}
tr > td:nth-child(1) {text-align: left}
tr > td:nth-child(2) {text-align: right}
</style>
<script id="flowerTmpl" type="text/x-handlebars-template">
{{#flowers}}
<div class="dcell">
<img src="{{product}}.png"/>
<label for="{{product}}">{{name}}:</label>
<input name="{{product}}" value="0" />
</div>
{{/flowers}}
</script>
<script id="productRowTmpl" type="text/x-handlebars-template">
{{#rows}}
<tr><td>{{name}}</td><td>{{quantity}}</td></tr>
{{/rows}}
</script>
<script type="text/javascript">
$(document).ready(function () {
$("<div id='popup'><div id='popupContent'><img src='progress.gif'"
+ "alt='progress'/><div>Placing Order</div></div></div>")
.appendTo("body");
$.ajaxSetup({
timeout: 5000,
converters: {"text html": function (data) { return $(data); }}
});
$(document).ajaxError(function (e, jqxhr, settings, errorMsg) {
$("#error").remove();
var msg = "An error occurred. Please try again"
if (errorMsg == "timeout") {
msg = "The request timed out. Please try again"
} else if (jqxhr.status == 404) {
msg = "The file could not be found";
}
$("<div id=error/>").text(msg).insertAfter("h1");
}).ajaxSuccess(function () {
$("#error").remove();
});
$("<a id=left></a><a id=right></a>").prependTo("#orderForm")
.addClass("arrowButton").click(handleArrowPress).hover(handleArrowMouse);
$("#right").appendTo("#orderForm");
$("#row2, #row3, #popup, #summaryForm").hide();
var flowerReq = $.get("flowers.html", function (data) {
var elems = data.filter("div").addClass("dcell");
elems.slice(0, 3).appendTo("#row1");
elems.slice(3).appendTo("#row2");
});
var jsonReq = $.getJSON("additionalflowers.json", function (data) {
$("#flowerTmpl").template({ flowers: data }).appendTo("#row3");
});
$("<div id=errorSummary>").text("Please correct the following errors:")
.append("<ul id='errorsList'></ul>").hide().insertAfter("h1");
$("#orderForm").validate({
highlight: function(element, errorClass) {
$(element).addClass("invalidElem");
},
unhighlight: function(element, errorClass) {
$(element).removeClass("invalidElem");
},
errorContainer: "#errorSummary",
errorLabelContainer: "#errorsList",
wrapper: "li",
errorElement: "div"
});
var plurals = {
aster: "Asters", daffodil: "Daffodils", rose: "Roses",
peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops",
carnation: "Carnations", lily: "Lillies", orchid: "Orchids"
};
$.when(flowerReq, jsonReq).then(function() {
$("input").each(function(index, elem) {
$(elem).rules("add", {
required: true,
min: 0,
digits: true,
remote: {
url: "http://node.jacquisflowershop.com/stockcheck",
type: "post",
global: false
},
messages: {
required: "Please enter a number of " + plurals[elem.name],
digits: "Please enter a number of" + plurals[elem.name],
min: "Please enter a positive number of "
+ plurals[elem.name]
}
})
}).change(function(e) {
if ($("#orderForm").validate().element($(e.target))) {
var total = 0;
$("input").each(function(index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
}
});
});
$("#orderForm button").click(function (e) {
e.preventDefault();
var formData = $("#orderForm").serialize();
$("body *").not("#popup, #popup *").css("opacity", 0.5);
$("input").attr("disabled", "disabled");
$("#popup").show();
$.ajax({
url: "http://node.jacquisflowershop.com/order",
type: "post",
data: formData,
dataType: "json",
dataFilter: function (data, dataType) {
data = $.parseJSON(data);
var cleanData = {
totalItems: data.totalItems,
totalPrice: data.totalPrice
};
delete data.totalPrice; delete data.totalItems;
cleanData.products = [];
for (prop in data) {
cleanData.products.push({
name: plurals[prop],
quantity: data[prop]
})
}
return cleanData;
},
converters: { "text json": function (data) { return data; } },
success: function (data) {
processServerResponse(data);
},
complete: function () {
$("body *").not("#popup, #popup *").css("opacity", 1);
$("input").removeAttr("disabled");
$("#popup").hide();
}
})
});
function processServerResponse(data) {
if (data.products.length > 0) {
$("body > *:not(h1)").hide();
$("#summaryForm").show();
$("#productRowTmpl")
.template({ rows: data.products }).appendTo("tbody");
$("#totalitems").text(data.totalItems);
$("#totalprice").text("$" + data.totalPrice);
} else {
var elem = $("input").get(0);
var err = new Object();
err[elem.name] = "No products selected";
$("#orderForm").validate().showErrors(err);
$(elem).removeClass("invalidElem");
}
}
var total = $("#buttonDiv")
.prepend("<div>Total Items: <span id=total>0</span></div>")
.css({ clear: "both", padding: "5px" });
$("<div id=bbox />").appendTo("body").append(total).css("clear: left");
$("input").change(function (e) {
var total = 0;
$("input").each(function (index, elem) {
total += Number($(elem).val());
});
$("#total").text(total);
});
function handleArrowMouse(e) {
var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
$(this).css("background-position", propValue);
}
function handleArrowPress(e) {
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex =
jQuery.inArray(visibleRow.attr("id"), elemSequence);
var targetRowIndex;
if (e.target.id == "left") {
targetRowIndex = visibleRowIndex - 1;
if (targetRowIndex < 0) { targetRowIndex = elemSequence.length - 1 };
} else {
targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
visibleRow.fadeOut("fast", function () {
$("#" + elemSequence[targetRowIndex]).fadeIn("fast")
});
}
});
</script>
</head>
<body>
<h1>Jacqui's Flower Shop</h1>
<form id="orderForm" method="post" action="[`node.jacquisflowershop.com/order`](http://node.jacquisflowershop.com/order)">
<div id="oblock">
<div class="dtable">
<div id="row1" class="drow"></div>
<div id="row2" class="drow"></div>
<div id="row3" class="drow"></div>
</div>
</div>
<div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
<form id="summaryForm" method="post" action="">
<div id="summary">
<h3>Order Summary</h3>
<table border="1">
<thead>
<tr><th>Product</th><th>Quantity</th>
</thead>
<tbody>
</tbody>
<tfoot>
<tr><th>Number of Items:</th><td id="totalitems"></td></tr>
<tr><th>Total Price:</th><td id="totalprice"></td></tr>
</tfoot>
</table>
<div id="buttonDiv2"><button type="submit">Complete Order</button></div>
</div>
</form>
</body>
</html>
添加新表单
我做的第一件事是向文档的静态 HTML 部分添加一个新表单,如下所示:
...
<form id="summaryForm" method="post" action="">
<div id="summary">
<h3>Order Summary</h3>
<table border="1">
<thead>
<tr><th>Product</th><th>Quantity</th>
</thead>
<tbody>
</tbody>
<tfoot>
<tr><th>Number of Items:</th><td id="totalitems"></td></tr>
<tr><th>Total Price:</th><td id="totalprice"></td></tr>
</tfoot>
</table>
<div id="buttonDiv2"><button type="submit">Complete Order</button></div>
</div>
</form>
...
这是新功能的核心。当用户向服务器提交他的产品选择时,这个form中的table将用于显示我从 Ajax 请求中返回的数据。
提示在前面的例子中,我一直使用
$("form")选择器,但是由于现在它们在文档中是两种形式,所以我将这些引用切换到使用form元素的id属性值。
我不想立即显示新表单,所以我将它添加到我在脚本中隐藏的元素列表中,如下所示:
...
$("#row2, #row3, #popup,#summaryForm").hide();
...
正如您现在所料,哪里有新元素,哪里就有新的 CSS 样式,如下所示:
...
#summary {text-align: center}
table {border-collapse: collapse; border: medium solid black; font-size: 18px;
margin: auto; margin-bottom: 5px;}
th {text-align: left}
th, td {padding: 2px}
tr > td:nth-child(1) {text-align: left}
tr > td:nth-child(2) {text-align: right}
...
这些样式确保表格显示在浏览器窗口的中间,并且表格列中的文本与正确的边缘对齐。
完成 Ajax 请求
下一步是完成对ajax请求的调用。
...
$("#orderForm button").click(function (e) {
e.preventDefault();
var formData = $("#orderForm").serialize();
$("body *").not("#popup, #popup *").css("opacity", 0.5);
$("input").attr("disabled", "disabled");
$("#popup").show();
$.ajax({
url: "http://node.jacquisflowershop.com/order",
type: "post",
data: formData,
dataType: "json",
dataFilter: function (data, dataType) {
data = $.parseJSON(data);
var cleanData = {
totalItems: data.totalItems,
totalPrice: data.totalPrice
};
delete data.totalPrice; delete data.totalItems;
cleanData.products = [];
for (prop in data) {
cleanData.products.push({
name: plurals[prop],
quantity: data[prop]
})
}
return cleanData;
},
converters: { "text json": function (data) { return data; } },
success: function (data) {
processServerResponse(data);
},
complete: function () {
$("body *").not("#popup, #popup *").css("opacity", 1);
$("input").removeAttr("disabled");
$("#popup").hide();
}
})
});
...
我删除了complete函数中的显式延迟,并在请求中添加了dataFilter、converters和success设置。我使用dataFilters设置来提供一个函数,将来自服务器的 JSON 数据转换成更有用的东西。服务器发送给我一个 JSON 字符串,如下所示:
{"aster":"4","daffodil":"1","snowdrop":"2","totalItems":7,"totalPrice":"15.93"}
我解析 JSON 数据并对其进行重组,得到如下结果:
{"totalItems":7,"totalPrice":"15.93",
"products":[{"name":"Asters","quantity":"4"}, {"name":"Daffodils","quantity":"1"},
{"name":"Snowdrops","quantity":"2"}]
}
这种格式给了我两个好处。首先,它更适合用于数据模板,因为我可以将products属性传递给template方法。第二,我可以检查用户是否选择了任何带有products.length的元素。这是两个很小的优点,但是我想尽可能地集成前面章节的特性。请注意,我还用复数名称(ochids)替换了产品的名称(例如orchid)。
已经将 JSON 数据解析成 JavaScript 对象(使用parseJSON方法,我在第三十四章中描述过),我想禁用内置的转换器,它会尝试做同样的事情。为此,我为 JSON 定义了一个定制的转换器,它只传递数据,不做任何修改。
...
converters: {"text json": function(data) { return data;}}
...
处理数据
对于ajax方法success回调,我指定了processServerResponse函数,我定义如下:
...
function processServerResponse(data) {
if (data.products.length > 0) {
$("body > *:not(h1)").hide();
$("#summaryForm").show();
$("#productRowTmpl").template({ rows: data.products }).appendTo("tbody");
$("#totalitems").text(data.totalItems);
$("#totalprice").text("$" + data.totalPrice);
} else {
var elem = $("input").get(0);
var err = new Object();
err[elem.name] = "No products selected";
$("#orderForm").validate().showErrors(err);
$(elem).removeClass("invalidElem");
}
}
...
如果来自服务器的数据包含产品信息,那么我隐藏文档中我不想要的所有元素(包括原来的form元素和我在脚本中添加的元素)并显示新的form。我使用下面的数据模板填充table:
...
<script id="productRowTmpl" type="text/x-handlebars-template">
{{#rows}}
<tr><td>{{name}}</td><td>{{quantity}}</td></tr>
{{/rows}}
</script>
...
该模板为每个选定的产品生成一个表格行。最后,我使用text方法设置显示总价和商品数量的单元格的内容。
...
$("#totalitems").text(data.totalItems);
$("#totalprice").text("$" + data.totalPrice);
...
然而,如果来自服务器的数据不包含任何产品信息(这表明用户将所有的input元素值都置为零),那么我会做一些不同的事情。首先,我选择第一个input元素,如下所示:
...
var elem = $("input").get(0);
...
然后,我创建一个包含属性的对象,该属性的名称是input元素的名称值,其值是给用户的消息。然后,我对form元素调用validate方法,对结果调用showErrors方法,如下所示:
...
var err = new Object();
err[elem.name] = "No products selected";
$("#orderForm").validate().showErrors(err);
...
这允许我手动将一个错误注入到验证系统中,并利用我之前设置的结构和格式。我必须提供一个元素的名称,这样验证插件就可以突出错误发生的地方,这并不理想,正如你在图 16-7 中看到的。
图 16-7 。显示选择错误
我正在显示一条一般的消息,但是高亮显示只应用于一个input元素,这会让用户感到困惑。为了解决这个问题,我删除了验证插件用于高亮显示的类,如下所示:
...
$(elem).removeClass("invalidElem");
...
这产生了图 16-8 中所示的效果。
图 16-8 。从与错误相关联的元素中移除突出显示
摘要
在这一章中,我重构了这个例子,把这本书这一部分涉及的主题和特性结合在一起。我广泛使用了 Ajax(使用速记和低级方法),应用了一对数据模板,并使用验证插件在本地和远程检查值(并手动显示错误)。在本书的下一部分,我将转向 jQuery UI,下一次我重构示例文档时,它将有一个非常不同的外观。