jQuery2-高级教程-五-

73 阅读24分钟

jQuery2 高级教程(五)

原文:Pro jQuery 2.0

协议:CC BY-NC-SA 4.0

十三、使用表单

在这一章中,我将展示 jQuery 对 HTML 表单的支持。在部分内容中,我将重述与表单相关的事件以及可以用来管理它们的 jQuery 方法,但是本章的大部分内容都是关于一个插件,它提供了一种很好的机制,可以在表单提交给服务器之前验证用户输入到表单中的值。如果您编写过任何一种基于表单的 web 应用,您就会意识到用户会将各种数据输入到表单中,因此验证是一个重要的过程。

本章一开始,我介绍了您将在本书的这一部分使用的Node.js服务器脚本。对于这一章,脚本除了向您显示输入到表单中的数据值之外没有做太多的事情,但是在后面的章节中,我将开始更多地依赖于Node.js。表 13-1 对本章进行了总结。

表 13-1 。章节总结

问题解决办法列表
设置Node.js服务器。使用本章中列出的脚本(包括在本书附带的源代码中)。1, 2
对焦点被一个form元素获得或失去做出响应。使用focusblur方法。three
响应用户输入到form元素中的值的变化。使用change方法。four
响应(并中断)提交表单的用户。使用submit方法。5, 6
验证表单中的值。使用验证插件。seven
配置验证插件。将地图对象传递给validate方法。eight
使用类定义和应用验证规则。使用addClassRulesaddClass方法。9–12
将验证规则直接应用于元素。使用rules方法。13, 14
使用元素名称应用验证规则。options对象添加一个rules属性。Fifteen
使用元素属性应用验证规则。定义对应于单个验证检查的属性。Sixteen
为通过元素名称和属性应用的规则定义自定义消息。向 options 对象添加一个message属性,设置为定义定制消息的 map 对象。17, 18
为直接应用于元素的规则定义自定义消息。包括一个 map 对象,它将消息定义为rules方法的一个参数。Nineteen
创建自定义验证检查。使用addMethod方法。20, 21
格式化验证消息。使用options对象的highlightunhighlighterrorElementerrorClass属性。22–26
使用验证摘要。使用errorContainererrorLabelContainer属性。Twenty-seven
使用模板编写错误信息。使用$.validator.format方法。Twenty-eight

准备 Node.js 服务器

在本章中,我将使用Node.js从浏览器接收和处理表单数据。我不想纠缠于Node.js如何工作的细节,但我选择它作为本书的一个原因是因为Node.js是围绕 JavaScript 构建的,这意味着你可以使用与客户端编程相同的技能进行服务器端编程。

image 提示如果你想重现本章中的例子,关于如何获取Node.js的详细信息,参见第一章。您可以从Apress.com下载formserver.js服务器端脚本以及本章的所有示例。

清单 13-1 显示了你将在本章中使用的服务器端脚本,我已经将它保存在一个名为formserver.js的文件中。我把它呈现为一个黑盒,只解释输入和输出。

清单 13-1 。formserver.js Node.js 脚本

var http = require("http");
var querystring = require("querystring");

var port = 80;

http.createServer(function (req, res) {
    console.log("[200 OK] " + req.method + " to " + req.url);

    if (req.method == "POST") {
        var dataObj = new Object();
        var cType = req.headers["content-type"];
        var fullBody = "";

        if (cType && cType.indexOf("application/x-www-form-urlencoded") > -1) {
            req.on("data", function(chunk) { fullBody += chunk.toString();});
            req.on("end", function() {
                res.writeHead(200, "OK", {"Content-Type": "text/html"});
                res.write("<html><head><title>Post data</title></head><body>");
                res.write("<style>th, td {text-align:left; padding:5px; color:black}\n");
                res.write("th {background-color:grey; color:white; min-width:10em}\n");
                res.write("td {background-color:lightgrey}\n");
                res.write("caption {font-weight:bold}</style>");
                res.write("<table border='1'><caption>Form Data</caption>");
                res.write("<tr><th>Name</th><th>Value</th>");
                var dBody = querystring.parse(fullBody);
                for (var prop in dBody) {
                    res.write("<tr><td>" + prop + "</td><td>"
                        + dBody[prop] + "</td></tr>");
                }
                res.write("</table></body></html>");
                res.end();
            });
        }
    }

}).listen(port);
console.log("Ready on port " + port);

要运行这个脚本,我在命令行输入以下内容:

node.exe formserver.js

如果您使用的是另一个操作系统,该命令会有所不同。详见Node.js文件。为了演示Node.js功能,我将使用清单 13-2 中显示的示例文档,我将它保存为example.html

清单 13-2 。本章的示例文档

<!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"/>
    <script id="flowerTmpl" type="text/x-handlebars-template">
        {{#each flowers}}
        <div class="dcell">
            <img src="{{product}}.png"/>
            <label for="{{product}}">{{name}}: </label>
            <input name="{{product}}" value="0" required />
        </div>
        {{/each}}
    </script>
    <script type="text/javascript">
        $(document).ready(function () {

            var data = { flowers: [
               { name: "Aster", product: "aster", stock: "10", price: "2.99" },
               { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
               { name: "Rose", product: "rose", stock: "2", price: "4.99" },
               { name: "Peony", product: "peony", stock: "0", price: "1.50" },
               { name: "Primula", product: "primula", stock: "1", price: "3.12" },
               { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
            };

            var templResult = $("#flowerTmpl").template(data).filter("*");
            templResult.slice(0, 3).appendTo("#row1");
            templResult.slice(3).appendTo("#row2");
        });
    </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>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

使用数据模板生成单个产品元素(如第十二章所述)。我已经为form元素的action属性指定了一个值,这意味着它将发送到以下 URL:

http://node.jacquisflowershop.com/order

我使用两个不同的服务器。第一个服务器(www.jacquisflowershop.com)是在整本书中提供 HTML 内容的服务器。它交付静态内容,如 HTML 文档、脚本文件和图像。对我来说,这是微软的 IIS,但你可以使用任何吸引你的服务器。(我使用 IIS 是因为我的很多书都是关于微软 web 技术的,而且我已经安装了一台服务器,随时可以使用。)

第二台服务器(node.jacquisflowershop.com)运行Node.js(使用前面显示的formserver.js脚本),当您提交示例文档中的form时,数据将被发送到这里。在这一章中,我不太关心服务器如何处理它接收到的数据:我将关注表单本身。在图 13-1 中,你可以看到我已经在文档的input元素中输入了一些值。

9781430263883_Fig13-01.jpg

图 13-1 。将数据输入输入元素

当我点击Place Order按钮时,表单被提交到Node.js服务器,一个简单的响应被发送回浏览器,如图图 13-2 所示。

9781430263883_Fig13-02.jpg

图 13-2 。来自 node . js 服务器的响应

我知道这不是一个有趣的回答,但是我现在只是需要一个地方来发送数据,我不想脱离服务器端开发的轨道。

概述表单事件方法

jQuery 包括一组处理表单相关事件的方法。现在有必要概括一下这些,因为你是专门来看表格的。表 13-2 描述了相应的方法和事件。

表 13-2 。jQuery 表单事件方法

方法事件描述
blur(function)Blurform元素失去焦点时触发。
change(function)Changeform元素的值改变时触发。
focus(function)Focus当焦点被给予一个form元素时触发。
select(function)Select当用户选择form元素中的文本时触发。
submit(function)Submit当用户想要提交表单时触发。

image 提示不要忘记 jQuery 定义了一组匹配表单元素的扩展选择器。详见第五章。

处理表单焦点

blurfocus方法允许您响应焦点的变化。这些功能的一个常见用途是通过强调哪个元素具有焦点(以及哪个元素将从键盘接收输入)来帮助用户。清单 13-3 提供了一个示范。

清单 13-3 。管理表单元素焦点

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        function handleFormFocus(e) {
            var borderVal = e.type == "focus" ? "medium solid green" : "";
            $(this).css("border", borderVal);
        }
        $("input").focus(handleFormFocus).blur(handleFormFocus);
    });
</script>
...

在这个例子中,我选择了所有的input元素,并将handleFormFocus函数注册为focusblur事件的处理程序。该函数在元素获得焦点时应用绿色边框,并在失去焦点时移除它。你可以在图 13-3 中看到效果。

9781430263883_Fig13-03.jpg

图 13-3 。强调重点元素

注意,我使用了input选择器。换句话说,我通过标签选择了元素。jQuery 提供了扩展选择器:input(我在第五章中描述了扩展选择器),但是扩展选择器匹配更广泛的元素,并且将匹配能够提交表单的按钮元素,这意味着如果我使用了使用扩展选择器,边界将被应用到button以及实际的input元素。您可以在图 13-4 中看到按钮聚焦时的差异。您使用哪个选择器是个人偏好的问题,但是了解其中的区别是很有用的。

9781430263883_Fig13-04.jpg

图 13-4 。输入和的区别:输入选择器

处理数值变化

当用户改变一个form元素中的值时,触发change事件。如果您基于表单中的值提供累积信息,这是一个有用的事件。清单 13-4 展示了如何使用该事件来跟踪花店文档中所选商品的总数。这也是我在本书第二部分末尾重构示例时采用的方法。

清单 13-4 。响应变更事件

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        function handleFormFocus(e) {
            var borderVal = e.type == "focus" ? "medium solid green" : "";
            $(this).css("border", borderVal);
        }
        $("input").focus(handleFormFocus).blur(handleFormFocus);

        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);
        });
    });
</script>
...

在这个例子中,我通过合计所有input元素中的值并在我之前添加到文档中的span元素中显示结果来响应 change 事件。

image 提示注意,我使用了val方法从input元素中获取值。

处理表单提交

您可以使用表单执行的许多更高级的活动都源于您可以阻止浏览器的默认表单机制工作的方式。清单 13-5 提供了一个简单的演示。

清单 13-5 。拦截表单提交

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").submit(function (e) {
            if ($("input").val() == 0) {
                e.preventDefault();
            }
        });
    });
</script>
...

我为submit事件注册了一个处理函数。当用户单击“下订单”按钮时,将触发此事件。如果第一个input元素的值是0,我调用preventDefault方法来中断表单的默认动作,即向服务器提交数据。对于任何其他值,提交表单。

image 提示作为替代,你可以从事件处理函数返回false来达到同样的效果。

以编程方式提交表单有两种不同的方式。您可以使用不带任何参数的 jQuery submit方法,也可以使用由 HTML5 规范为form元素定义的submit方法。清单 13-6 显示了使用中的两种方法。

清单 13-6 。显式提交表单

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").submit(function (e) {
            if ($("input").val() == 0) {
                e.preventDefault();
            }
        });

        $("<button>jQuery Method</button>").appendTo("#buttonDiv").click(function (e) {
            $("form").submit();
            e.preventDefault();
        });

        $("<button>DOM API</button>").appendTo("#buttonDiv").click(function (e) {
            document.getElementsByTagName("form")[0].submit();
            e.preventDefault();
        });
    });
</script>
...

我在文档中添加了两个元素button??。使用 jQuery submit方法的那个触发提交事件,在上一个例子中我为它设置了一个处理函数。这意味着如果第一个 input 元素的值为零,就不会提交表单。

使用 DOM API 并调用由form元素定义的submit方法的button元素有效地绕过了事件处理程序,因为submit事件没有被触发,这意味着无论first输入元素的值是多少,表单都会被提交。

image 提示当然,我的建议是坚持使用 jQuery 方法,但是如果你使用 DOM 方法,至少你会理解你得到的结果。

验证表单值

中断和阻止浏览器向服务器提交数据的主要原因是,您希望验证用户输入到表单中的值。在某种程度上,每个 web 程序员都意识到用户会在一个input元素中输入任何东西,假设你的用户会提供有用且有意义的数据是不明智的。您可能需要处理无数不同的值,但是以我的经验来看,用户在表单中给你一些意想不到的东西只有几个原因。

第一个原因是用户不了解你在追求什么数据。例如,您可能要求输入信用卡上的姓名,但是用户可能输入了她的卡号。

第二个原因是用户不想给你你所要求的信息,只是想尽快通过表单。她会参加任何能让她进入下一阶段的活动。如果您有很多新用户的电子邮件地址是a@a.com,那么您知道这就是问题所在。

第三个原因是你在询问用户没有的信息,比如问一个英国居民他住在哪个州。(我们这里没有州。我在看着你,NPR。不给你捐款。)

最后一个原因是用户犯了一个真正的错误,通常是打字错误。例如,我是一个快速但不准确的打字员,我经常把我的姓打成Freman而不是Freeman,漏掉了一个e

对于拼写错误你无能为力,但是你处理其他三个原因的方式可以决定创建一个流畅无缝的应用还是会惹恼用户。

我不想长篇大论地谈论网页表单的设计,但是我想说解决这个问题的最好方法是关注用户想要达到的目标。当事情出错时,试着从用户的角度来看待问题(以及所需的解决方案)。您的用户不知道您是如何构建系统的,他们也不关心您的业务流程;他们只想完成一些事情。如果你把注意力放在用户试图完成的任务上,并且当她没有给你想要的数据时不要不必要的惩罚她,每个人都会很开心。

jQuery 为您提供了创建自己的系统来验证数据值所需的所有工具,但是我推荐一种不同的方法。最流行的 jQuery 插件之一叫做 Validation,正如您从名称中猜到的,它处理表单验证。

image 注意我在本章讨论的是客户端验证。这是对服务器端验证的补充,而不是替代,在服务器端验证中,当服务器接收到数据时,您会对其进行检查。客户端验证是为了用户的利益:让他不必重复向服务器提交数据来发现和纠正数据错误。服务器端验证有利于应用并确保坏数据不会导致问题。您必须两者都使用:绕过客户端验证是微不足道的,并且它不能为您的应用提供可靠的保护。

你可以从http://jqueryvalidation.org 下载验证插件,或者使用我在本书的源代码下载中包含的版本(可在 Apress.com 的获得)。清单 13-7 展示了这个插件的使用。(我写这篇文章的时候,当前版本是 1.1.1。)

清单 13-7 。使用表单验证插件

<!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">
        .errorMsg {color: red}
        .invalidElem {border: medium solid red}
    </style>
    <script id="flowerTmpl" type="text/x-handlebars-template">
        {{#each flowers}}
        <div class="dcell">
            <img src="{{product}}.png"/>
            <label for="{{product}}">{{name}}: </label>
            <input name="{{product}}" value="0" required />
        </div>
        {{/each}}
    </script>
    <script type="text/javascript">
        $(document).ready(function () {

            var data = { flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
            };

            var templResult = $("#flowerTmpl").template(data).filter("*");
            templResult.slice(0, 3).appendTo("#row1");
            templResult.slice(3).appendTo("#row2");

            $("form").validate({
                highlight: function (element, errorClass) {
                    $(element).add($(element).parent()).addClass("invalidElem");
                },
                unhighlight: function (element, errorClass) {
                    $(element).add($(element).parent()).removeClass("invalidElem");
                },
                errorElement: "div",
                errorClass: "errorMsg"
            });

            $.validator.addClassRules({
                flowerValidation: {
                    min: 0
                }
            })

            $("input").addClass("flowerValidation").change(function (e) {
                $("form").validate().element($(e.target));
            });
        });
    </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>

插件以 zip 文件的形式分发,您需要对其进行解压缩。从dist文件夹中复制jquery.validate.js文件,使其与example.html文件在同一个目录下。

image 提示验证插件有很多不同的配置选项。在这一章中,我把重点放在了那些最常用的和最广泛的情况上。如果它们不适合您,我建议您探索一些其他选项,插件下载中的文档中描述了这些选项。

image 注意 HTML5 包括对一些表单验证的支持。这是一个好的开始,但它非常基础,浏览器解释规范的方式仍然存在显著的差异。在 HTML5 特性的范围、丰富性和一致性得到改善之前,我建议继续使用 jQuery 进行表单验证。

导入 JavaScript 文件

我要做的第一件事是用一个script元素将模板插件 JavaScript 文件引入文档,如下所示:

...
<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>
...

image 提示我使用过该文件的调试版本,但有一个最小化版本可用,一些 CDN 服务托管该文件,因为它非常受欢迎。

配置验证

下一步是配置form元素的验证,我通过对应该执行验证的form元素调用validate方法来完成。validate方法的参数是一个包含配置设置的 map 对象,如清单 13-8 所示。

清单 13-8 。配置验证

...
$("form").validate({
    highlight: function(element, errorClass) {
        $(element).add($(element).parent()).addClass("invalidElem");
    },
    unhighlight: function(element, errorClass) {
        $(element).add($(element).parent()).removeClass("invalidElem");
    },
    errorElement: "div",
    errorClass: "errorMsg"
});
...

我为四个选项指定了值(highlightunhighlighterrorElementerrorClass);我将在本章的后面回到这些并解释它们的含义。

定义验证规则

验证插件的灵活性很大程度上来自于测试有效输入的规则可以快速而容易地定义。将规则与元素关联起来有多种方式,我倾向于使用的一种方式是通过类来实现的。我定义了一组规则,并将它们与一个类相关联,当表单被验证时,这些规则被应用于包含在指定类成员中的任何元素。我在示例中创建了一个规则,如清单 13-9 中的所示。

清单 13-9 。定义验证规则

...
$.validator.addClassRules({
    flowerValidation: {
        min: 0
    }
})
...

在本例中,我创建了一个规则,它将应用于属于flowerValidation类的元素。规则是该值应该等于或大于零。我已经用min表达了规则中的条件。这只是验证插件提供的许多方便的预定义检查中的一个,我将在本章后面描述所有这些检查。

应用验证规则

通过将元素添加到上一步指定的类中,验证规则与表单中的元素相关联。这提供了为表单中不同种类的元素定制验证的能力。对于这个例子,所有的元素都被同等对待,所以我使用 jQuery 来选择所有的input元素并将它们添加到flowerValidation类中,如清单 13-10 所示。

清单 13-10 。将输入元素添加到与验证关联的类中

...
$("input").addClass("flowerValidation").change(function(e) {
    $("form").validate().element($(e.target));
});
...

我还为change事件设置了一个处理函数,以显式验证值已更改的元素。这确保了用户在纠正错误时能够立即得到反馈。你可以在图 13-5 中看到验证插件的效果。为了创建这个图,我在其中一个输入字段中输入了-1,并单击了 Place Order 按钮。

9781430263883_Fig13-05.jpg

图 13-5 。使用验证插件

image 提示显示给用户的消息文本由验证插件生成。我将在本章的后面向您展示如何定制这些消息。

验证插件显示一条错误消息,在问题解决之前,用户无法提交表单。错误消息为用户提供了如何解决问题的指导。(默认的消息,比如图中显示的,有点普通,但是在本章的后面我会告诉你如何改变文本。)

使用验证检查

验证插件支持多种检查,您可以使用这些检查来验证表单值。您在前面的示例中看到了min检查。这确保了该值大于或等于指定的数值。表 13-3 描述了您可以执行的一组检查。

表 13-3 。验证插件检查

检查描述
creditcard: true该值必须包含信用卡号。
date: true该值必须是有效的 JavaScript 日期。
digits: true该值必须仅包含数字。
email: true该值必须是有效的电子邮件地址。
max: maxVal该值必须至少与maxVal一样大。
maxlength: length该值不得超过length个字符。
min: minVal该值必须至少与minVal一样大。
minlength: length;该值必须至少包含length个字符。
number: true该值必须是十进制数。
range: [minVal, maxVal]该值必须在minValmaxVal之间。
rangelength: [minLen, maxLen]该值必须至少包含minLen个字符,不超过maxLen个字符。
required: true;需要一个值。
url: true该值必须是 URL。

您可以在单个规则中将多个规则关联在一起。这允许您以一种紧凑且富于表现力的方式执行复杂的验证。

image 提示验证插件发行版 zip 文件中包含一个名为additional-methods.js的文件。该文件定义了一些额外的检查,包括美国和英国的电话号码、IPv4 和 IPv6 地址,以及一些额外的日期、电子邮件和 URL 格式。

您可以通过几种方式将这些检查应用于您的元素。我将在接下来的章节中描述每一个。

image 注意验证插件还支持远程验证,用户输入到字段中的数据通过远程服务器进行检查。当您需要检查不能分发给客户机的数据时,这是很有用的,因为这样既不安全也不实际(例如检查用户名是否还没有被使用)。在第十四章和第十五章中介绍了远程验证所依赖的特性之后,我将在第十六章的中演示远程验证。

通过类应用验证规则

正如我前面解释的,我最常用的验证技术是通过类应用检查,这是我在例子中采用的方法。然而,我不局限于单个检查,我可以一起应用多个检查来验证用户提供的价值的不同方面,如清单 13-11 所示。

清单 13-11 。将多项检查合并到一个规则中

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });

        $.validator.addClassRules({
            flowerValidation: {
                required: true,
                digits: true,
                min: 0,
                max: 100
            }
        });

        $("input").addClass("flowerValidation").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

在这个例子中,我组合了requireddigitsminmax检查,以确保用户提供的值只包含数字,并且在 0 到 100 的范围内。

注意,我使用addClassRules方法将规则与类关联起来。此方法的参数是一组或多组检查以及它们要应用到的类名。如图所示,在 jQuery $主函数的validator属性上调用addClassRules方法。

每一个被验证的form元素都被单独评估,这意味着对于不同的问题,用户可以看到不同的错误信息,如图 13-6 中的所示。

9781430263883_Fig13-06.jpg

图 13-6 。对表单元素应用多重验证检查

我输入了几个无法通过检查的值。请务必注意,检查是按照规则中定义的顺序执行的。如果您查看Rose产品的错误消息,您会发现它没有通过digits检查。如果您重新安排检查的顺序,您可能会得到不同的错误。清单 13-12 显示了以不同顺序排列的验证检查。

清单 13-12 。更改应用检查的顺序

...
$.validator.addClassRules({
    flowerValidation: {
        required: true,
        min: 0,
        max: 100,
        digits: true
    }
})
...

在本例中,我将数字检查移到了规则的末尾。如果我现在将-1输入到一个表单字段中,将会失败的是min检查,如图图 13-7 所示。

9781430263883_Fig13-07.jpg

图 13-7 。更改验证期间应用检查的顺序

将验证规则直接应用于元素

下一个技术允许你对单个元素应用规则,如清单 13-13 所示。

清单 13-13 。将验证规则应用于选择中的元素

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });

        $.validator.addClassRules({
            flowerValidation: {
                required: true,
                min: 0,
                max: 100,
                digits: true,
            }
        })

        $("#row1 input").each(function (index, elem) {
            $(elem).rules("add", {
                min: 10,
                max: 20
            })
        });

        $("input").addClass("flowerValidation").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

我在一个jQuery对象上调用rules方法,传入字符串add和一个带有您想要执行的检查及其参数的地图对象。rules方法只对选择中的第一个元素起作用,所以我必须使用each方法来更广泛地应用规则。在本例中,我选择了所有作为row1元素的后代的input元素,并应用了minmax检查来确保用户输入了一个介于 10 和 20 之间的值。

image 提示当你调用rules方法时,你可以通过用remove替换add来删除元素中的规则。

使用rules方法应用于元素的规则在使用类应用规则之前被评估。对于我的例子,这意味着最上面一行的input元素将使用10min值和20max值进行检查,而其他输入元素将分别使用0100的值。你可以在图 13-8 中看到这样做的效果。

9781430263883_Fig13-08.jpg

图 13-8 。将规则直接应用于元素

因为我在单独处理每个元素的验证,所以我可以进一步定制检查,如清单 13-14 所示。

清单 13-14 。裁剪元素的检查

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });

        $("input").each(function (index, elem) {
            var rules = {
                required: true,
                min: 0,
                max: data.flowers[index].stock,
                digits: true
            }
            if (Number(data.flowers[index].price) > 3.00) {
                rules.max--;
            }
            $(elem).rules("add", rules);
        });

        $("input").addClass("flowerValidation").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

在这个例子中,我使用添加到文档中的数据对象来定制max检查的值,以便使用模板生成元素。根据stock属性设置max支票的值,如果价格大于 3 美元,则向下调整。当您拥有这样的数据时,您就能够执行更有用的验证。你可以在图 13-9 的中看到这种变化的效果。

9781430263883_Fig13-09.jpg

图 13-9 。根据数据为验证检查设置不同的值

通过元素名称属性应用验证规则

验证规则也可以基于name属性的值应用于元素。HTML 规范中没有要求name属性值是惟一的,单个值通常用于对一组form元素进行分类。在我的花店示例文档中,每个名称都是不同的,并且对应于特定的产品。无论哪种方式,您都可以创建对应于一个name属性值的规则,以及应用于所有被赋予该值的元素的规则。清单 13-15 给出了一个演示。

清单 13-15 。根据元素名称分配验证规则

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        var rulesList = new Object();
        for (var i = 0; i < data.flowers.length; i++) {
            rulesList[data.flowers[i].product] = {
                min: 0,
                max: Number(data.flowers[i].stock),
            }
        }

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
            rules: rulesList
        });

        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });

    });
</script>
...

当我设置form验证时,我使用传递给validate方法的配置对象的rules属性添加了依赖于元素名称的规则。请注意,我只使用了数据对象来创建规则集,并且数据对象中的product属性用于在input元素上生成name属性。还要注意,我必须使用Number来转换字符串数据值,以便正确处理它。

image 提示我倾向于不在自己的项目中使用这种方法,因为我更愿意直接处理文档中的元素,但是如果您有一个数据对象,并且希望在表单元素被添加到文档之前设置验证,这种技术会很方便。

使用元素属性应用验证规则

对元素应用验证检查的最后一种方法是使用属性。验证插件检查form元素以查看它们是否定义了与内置检查名称相对应的属性,因此定义了required属性的元素被认为需要所需的检查。清单 13-16 提供了一个演示。

清单 13-16 。使用元素属性执行验证

...
<script id="flowerTmpl" type="text/x-handlebars-template">
    {{#each flowers}}
    <div class="dcell">
        <img src="{{product}}.png"/>
        <label for="{{product}}">{{name}}: </label>
        <input name="{{product}}" value="0" required min="0" max="{{stock}}"/>
    </div>
    {{/each}}
</script>
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });

        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

当它与数据模板结合使用时,我喜欢这种技术,但我发现当应用于静态定义的元素时,它会使文档变得混乱,因为相同的属性被一次又一次地应用于元素。

指定验证消息

验证插件为所有的内置检查定义了一个默认的错误消息,但是这些都是通用的,并不总是对用户有用。举个简单的例子,如果我设置一个值为10max检查,并且用户在字段中输入20,那么错误消息将如下所示:

Please enter a value less than or equal to 12

这条消息描述了您在form元素上应用的约束,但是它没有向用户提供任何关于为什么有限制的指导。幸运的是,您可以更改这些消息以提供一些额外的上下文,并根据您的需要定制消息。用于更改消息的方法取决于最初创建验证规则的方式。当您使用类应用规则时,不可能更改消息,但是在下面的部分中,我将描述如何为其他技术定义定制消息。

为属性和名称验证指定消息

当依靠name属性或检查属性将规则与元素相关联时,您可以通过向传递给validate方法的options对象添加一个messages属性来更改显示给用户的消息。清单 13-17 提供了一个演示。

清单 13-17 。使用 options 对象的 messages 属性

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
            messages: {
                rose: { max: "We don't have that many roses in stock!" },
                primula: { max: "We don't have that many primulas in stock!" }
            }
        });

        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

本例的验证是通过应用于模板中的input元素的minmax属性来应用的,您可以看到 JavaScript 代码中设置了messages属性值的对象的结构。

在 messages 对象中,我使用我感兴趣的元素的名称定义了一个属性,并将该属性的值设置为支票和您想要使用的新消息之间的映射。在这个例子中,我已经修改了名称为roseprimula的元素上的max检查的消息。你可以在图 13-10 中看到这个效果,它说明了这些自定义信息是如何显示的。

9781430263883_Fig13-10.jpg

图 13-10 。通过选项对象更改消息

设置这些验证消息的语法可能是重复的,所以我倾向于用我想要的编程消息创建一个对象,如清单 13-18 所示。

清单 13-18 。以编程方式定义自定义消息

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        var customMessages = new Object();
        for (var i = 0; i < data.flowers.length; i++) {
            customMessages[data.flowers[i].product] = {
                max: "We only have " + data.flowers[i].stock + " in stock"
            }
        }

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
            messages: customMessages
        });

        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

在这个例子中,我结合了数据对象的stock属性来给用户一个更有意义的消息,如图 13-11 中的所示。

9781430263883_Fig13-11.jpg

图 13-11 。以编程方式生成自定义验证消息

为每个元素的验证指定消息

当对单个元素应用规则时,您可以传入一个messages对象,该对象定义了您希望用于检查的消息。清单 13-19 展示了这是如何做到的。

清单 13-19 。为基于每个元素应用的规则指定消息

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
        });

        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        }).each(function (index, elem) {
            $(elem).rules("add", {
                messages: {
                    max: "We only have " + data.flowers[index].stock + " in stock"
                }
            })
        });
    });
</script>
...

我再次使用了来自相应 flowers 数据对象的stock属性来定义消息。为了简单起见,我假设input元素的排序方式与数据项的排序方式相同。你可以在图 13-12 中看到这些信息的效果。

9781430263883_Fig13-12.jpg

图 13-12 。指定从数据对象派生的消息

image 提示我只使用 JavaScript 指定了消息。最小和最大规则仍然通过模板应用于输入元素,如清单 13-17 所示。

创建自定义

如果内置的验证检查不符合您的需要,您可以创建自定义的验证检查。这是一个简单的过程,意味着您可以将验证与 web 应用的结构和性质紧密联系起来。清单 13-20 提供了一个演示。

清单 13-20 。创建自定义验证检查

...
<script id="flowerTmpl" type="text/x-handlebars-template">
    {{#each flowers}}
    <div class="dcell">
        <img src="{{product}}.png"/>
        <label for="{{product}}">{{name}}: </label>
        <input name="{{product}}" value="0" required />
    </div>
    {{/each}}
</script>
<script type="text/javascript">
    $(document).ready(function () {

        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });

        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) < Number(args);
        }, "We don't have that many in stock");

        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: data.flowers[index].stock
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

我已经从模板的 input 元素中删除了 min 和 max 属性,并在 JavaScript 代码中引入了一个定制的验证检查。(您可以自由地混合和匹配定制的和内置的验证,但是这个清单中的例子复制了max验证器的功能。)

使用addMethod方法创建定制检查,该方法在$函数的validator属性上调用。此方法的参数是要分配给检查的名称、用于执行验证的函数以及显示验证失败的消息。在这个例子中,我定义了一个名为stock的检查,我将在接下来的章节中解释它。

定义验证功能

自定义验证函数的参数是用户输入的值、表示表单元素的HTMLElement对象,以及在对元素进行验证时指定的任何参数,如下所示:

...
$(elem).rules("add", {
    min: 0,
    stock:data.flowers[index].stock
})
...

当我应用规则时,我指定了来自 flower 数据对象的一个stock属性的值作为检查的参数,该属性对应于 input 元素。这将按原样传递给自定义验证函数:

...
function(value, elem,args) {
    return Number(value) <= Number(args);
}
...

值和参数以字符串的形式呈现,这意味着我必须使用Number类型来确保 JavaScript 正确地比较数值。验证函数的结果表明值是否有效——对于可接受的值,返回true,对于不可接受的值,返回false。对于我的函数,如果一个值小于或等于参数,那么它就是有效的。

定义验证消息

您可以指定以两种方式显示的消息。第一种是作为字符串,这是我在前面的例子中使用的。指定消息的另一种方式是使用函数,允许您创建具有更多上下文的消息。清单 13-21 提供了一个演示。

清单 13-21 。使用函数为自定义检查创建消息

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });

        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) < Number(args);
        }, function(args) {
            return "We only have " + args + " in stock"
        });

        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: data.flowers[index].stock
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

传递给函数的参数是您在应用规则时提供的参数,在本例中是来自 data flower 对象的stock属性值。你可以在图 13-13 中看到效果。

9781430263883_Fig13-13.jpg

图 13-13 。使用函数定义自定义检查的错误消息

格式化验证错误显示

在我看来,验证插件最好的特性之一是有多种方式可以配置如何向用户显示验证错误消息。在本章到目前为止的例子中,我依赖于清单 13-22 中突出显示的配置选项。

清单 13-22 。用于格式化验证错误的配置选项

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });

        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) < Number(args);
        }, function(args) {
            return "We only have " + args + " in stock"
        });

        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: data.flowers[index].stock
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

我依赖于四种不同的配置选项,但是它们紧密地耦合在一起。我将在下面的章节中解释每一个的重要性。

设置无效元素的类别

errorClass选项指定了一个与无效值相关联的类。当错误消息元素添加到文档中时,该类应用于这些错误消息元素。在我的例子中,我指定了一个名为errorMsg的类,它在style元素中有一个对应的 CSS 样式,如清单 13-23 所示。样式将文本颜色设置为红色,以强调验证错误。

清单 13-23 。示例文档的样式元素

...
<style type="text/css">
    .errorMsg {color: red}
    .invalidElem {border: medium solid red}
</style>
...

设置错误信息元素

错误消息作为包含无效值的form元素的下一个兄弟元素插入到文档中。默认情况下,错误消息文本包含在一个label元素中。这在示例中并不适合我,因为外部样式表已经包含了一个选择器,它匹配 CSS 表格布局中单元格级div元素内的所有label元素,并且应用了一个阻止文本正确显示的样式。为了解决这个问题,我使用了errorElement选项来指定使用一个div元素,如清单 13-24 所示。

清单 13-24 。指定将用于错误信息的元素

...
$("form").validate({
    highlight: function(element, errorClass) {
        $(element).add($(element).parent()).addClass("invalidElem");
    },
    unhighlight: function(element, errorClass) {
        $(element).add($(element).parent()).removeClass("invalidElem");
    },
    errorElement: "div",
    errorClass: "errorMsg",

});
...

设置无效元素的高亮显示

highlightunhighlight选项指定用于突出显示包含无效值的元素的功能。函数的参数是代表无效元素的HTMLElement对象和使用errorClass选项指定的类。

正如您在清单 13-25 中看到的,我忽略了第二个属性,但是使用HTMLElement对象创建一个 jQuery 选择,导航到父元素,并将其添加到invalidElem类中。

清单 13-25 。控制元素高亮显示

...
$("form").validate({
    highlight: function(element, errorClass) {
        $(element).add($(element).parent()).addClass("invalidElem");
    },
    unhighlight: function(element, errorClass) {
        $(element).add($(element).parent()).removeClass("invalidElem");
    },
    errorElement: "div",
    errorClass: "errorMsg",

});
...

当用户纠正问题并且元素包含有效值时,调用由unhighlight选项指定的函数。我利用这个机会删除我在另一个函数中添加的类。invalidElem类对应于文档中包含的style元素中的一个选择器,如清单 13-26 中的所示。

清单 13-26 。用于突出显示元素的样式

...
<style type="text/css">
    .errorMsg {color: red}
    .invalidElem {border: medium solid red}
</style>
...

您可以按照自己喜欢的任何方式选择和操作这些函数中的元素。我已经对父元素应用了边框,但是如果我愿意,我也可以直接对元素本身或整个文档的另一部分进行操作。

使用验证摘要

验证插件可以向用户显示所有验证错误的单一列表,而不是在每个元素旁边添加单独的消息。如果文档的结构或布局不容易伸缩以容纳额外的元素,这将非常有用。清单 13-27 展示了如何创建一个验证摘要。

清单 13-27 。使用验证摘要

...
<script type="text/javascript">
    $(document).ready(function () {

        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };

        var plurals = {
            aster: "Asters", daffodil: "Daffodils", rose: "Roses",
            peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops"
        };

        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");

        $("<div id='errorSummary'>Please correct the following errors:</div>")
            .addClass("errorMsg invalidElem")
            .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"
        });

        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) <= Number(args.data.stock);
        }, function (args) {
            return "You requested " + $(args.element).val() + " "
                + plurals[args.data.product] + " but we only have "
                + args.data.stock + " in stock";
        });

        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: {
                    index: index,
                    data: data.flowers[index],
                    element: elem
                }
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });

    });
</script>
...

对于这个例子,我将反向工作,在解释我如何到达那里之前向您展示结果。图 13-14 显示了正在显示的验证摘要。

9781430263883_Fig13-14.jpg

图 13-14 。使用验证摘要

准备验证消息

使用验证摘要时要解决的第一个问题是,将错误消息放在form元素旁边所隐含的上下文会丢失;我必须对错误消息做一些额外的工作,以便它们有意义。首先,我定义了一个包含花名复数的对象:

...
var plurals = {
    aster: "Asters", daffodil: "Daffodils", rose: "Roses",
    peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops"
}
...

我使用自定义检查的函数特性,使用这些值生成特定的错误消息,如下所示:

...
$.validator.addMethod("stock", function (value, elem, args) {
    return Number(value) <= Number(args.data.stock);
}, function (args) {
    return "You requested " + $(args.element).val() + " "
        + plurals[args.data.product] + " but we only have "
        + args.data.stock + " in stock";
});
...

这两个阶段之间的联系是我在对form元素应用自定义检查时指定的参数对象。内置检查具有简单的参数,但是您可以创建复杂的对象并传递任何适合您的数据,如下所示:

...
$("input").each(function (index, elem) {
    $(elem).rules("add", {
        stock: {
            index: index,
            data: data.flowers[index],
            element: elem
        }
    })
}).change(function (e) {
    $("form").validate().element($(e.target));
});
...

在本例中,我已经传递了索引、数据数组和元素本身,所有这些我都用来拼凑消息以显示给用户。(在本章的后面,我将向您展示一个简化字符串组合的有用特性。)

创建验证摘要

我负责创建包含验证摘要的元素,并将其添加到文档中。为此,我添加了一个包含一个ul元素的div元素。我的目标是创建一个显示每个错误的无编号列表:

...
$("<div id='errorSummary'>Please correct the following errors:</div>")
    .addClass("errorMsg invalidElem").append("<ul id='errorsList'></ul>").hide().insertAfter("h1");
...

div元素中的文本显示在错误列表的上方。注意,在将这些元素添加到 DOM 之后,我使用了hide方法。我不仅负责创建元素,还负责确保在没有错误时它们是不可见的。hide方法确保了用户最初看不到验证摘要——一旦验证过程开始,验证插件将负责查看。

现在我已经准备好了所有的部分,我可以配置验证摘要,如下所示:

...
$("form").validate({
    highlight: function (element, errorClass) {
        $(element).addClass("invalidElem");
    },
    unhighlight: function (element, errorClass) {
        $(element).removeClass("invalidElem");
    },
    errorContainer: "#errorSummary",
    errorLabelContainer: "#errorsList",
    wrapper: "li",
    errorElement: "div"
});
...

我已经修改了hightlightunhighlight函数的实现,只设计了input元素的样式。errorContainer选项指定了一个选择器,当有验证错误要显示时,该选择器将变得可见。在我的例子中,这是 ID 为errorSummary的元素(即div元素)。errorLabelContainer选项指定单个错误消息将被插入的元素。对于我的例子,这是ul元素,因为我希望我的消息显示为列表。

wrapper选项指定验证消息将被插入的元素。这仅在您需要列表显示时有用。最后,errorElement指定了包含错误文本的元素。默认情况下,这是label元素,但是为了使格式化更容易,我已经切换到了div元素。这些选项的结果就是我在图 13-14 中展示给你的验证总结。

当用户解决一个问题时,验证插件从摘要中删除消息,当根本没有问题时,验证摘要完全隐藏,用户可以提交表单。图 13-15 显示了解决了上图中三个错误中的两个后的验证总结。

9781430263883_Fig13-15.jpg

图 13-15 。显示较少错误消息的验证摘要

内联消息和验证摘要之间的选择是个人的选择,通常由文档的结构决定。好消息是验证插件非常灵活,定义和应用完全符合您需求的验证通常不需要太多工作。

整理错误消息组合

我将在本章中做最后一个改动,只是为了演示验证插件的一个有用特性,它与验证数据没有直接关系。在前面的示例中,当我想要创建上下文错误消息时,我通过连接字符串和变量来实现,如下所示:

...
$.validator.addMethod("stock", function (value, elem, args) {
    return Number(value) <= Number(args.data.stock);
}, function (args) {
    return "You requested " + $(args.element).val() + " "
        + plurals[args.data.product] + " but we only have "
        + args.data.stock + " in stock";
});
...

这个可以,但是很难看,很难读。验证插件方法提供了一个格式化程序,其工作方式类似于 C#等语言中的字符串合成,你可以在清单 13-28 中看到我是如何使用这个特性的。

清单 13-28 。使用 jQuery 验证器字符串格式化特性

...
$.validator.addMethod("stock", function (value, elem, args) {
    return Number(value) <= Number(args.data.stock);
}, function(args) {
    return $.validator.format("You requested {0} {1} but we only have {2} in stock",
        $(args.element).val(), plurals[args.data.product], args.data.stock )
});
...

字符串组合由$.validator.format方法执行,该方法采用一个模板字符串和一些值参数。对模板字符串进行解析,寻找整数周围的大括号字符,比如{0},这些字符将被相应的 value 参数替换。第一个值参数由{0}引用,第二个由{1}引用,依此类推。$.validator.format方法返回一个函数,直到显示错误消息时才进行计算,这确保了在组成字符串时使用了正确的值。

如果您不习惯,这可能是一种奇怪的创建字符串的方式,但是如果您习惯了像 C#这样经常依赖这种方式来组成字符串的语言,这将是一个非常缺少的特性。

摘要

在本章中,我向您展示了 jQuery 为表单提供的支持。我首先概述了与表单相关的事件方法,并解释了在 HTML 表单的生命周期中最重要的事件方法所扮演的角色。本章的大部分时间都在讨论验证插件,它为验证用户输入表单的值提供了灵活且可扩展的支持,并提供了在数据提交到服务器之前解决任何问题的方法。在第十四章中,我开始描述 jQuery 为 Ajax 请求提供的支持。

十四、使用 Ajax:第一部分

Ajax 代表异步 JavaScript 和 XML,但如今通常是一个独立的词。Ajax 允许您异步向服务器发出请求,简而言之,这意味着您的请求发生在后台,不会阻止用户与 HTML 文档中的内容进行交互。Ajax 最常见的用途是从一个form元素提交数据。这样做的好处是浏览器不需要加载新的文档来显示服务器的响应,你可以使用标准的 jQuery 函数无缝地显示文档中的数据。

我在本章中使用的 Ajax 支持是内置在核心 jQuery 库中的,尽管我在本章末尾简要描述了一个有用的插件。jQuery 没有重新发明 Ajax,而是让现有的浏览器 Ajax API(应用编程接口)更容易使用。在这一章中,我描述了简写便利 Ajax 方法。这些更简单的方法使得使用 Ajax 变得相对快速和容易。在第十五章中,我描述了这些方法所基于的底层 jQuery Ajax API。然而,正如您将看到的,低级 API 并不是那么低级,使用它的主要原因是当简写和方便的方法不完全符合您的需要时。表 14-1 提供了本章的总结。

表 14-1 。章节总结

问题解决办法列表
执行异步 HTTP GET请求使用get方法1–3
处理从 Ajax GET请求中获得的数据将函数传递给get方法four
执行 Ajax 请求以响应用户操作在事件处理程序中调用get方法five
从服务器请求 JSON 数据使用get方法并在参数函数中接收一个对象6, 7
将数据作为GET请求的一部分发送到服务器将 JavaScript 对象作为参数传递给get方法eight
执行异步 HTTP POST请求使用post方法9, 10
POST请求中发送非表单数据将任何 JavaScript 对象作为参数传递给post方法Eleven
覆盖服务器在响应 Ajax 请求时指定的数据类型将预期类型作为参数传递给getpost方法12–13
避免最常见的 Ajax 陷阱不要认为 Ajax 请求是同步的Fourteen
使用方便的方法对特定的数据类型进行GET请求使用loadgetScriptgetJSON方法15–22
轻松为form元素启用 Ajax使用 Ajax 表单插件Twenty-three

使用 Ajax 速记方法

尽管 Ajax 通常与提交表单数据相关联,但它的应用范围要广得多。我将通过执行一些简单的任务来开始介绍 Ajax,从根本不使用表单就可以从服务器获取数据的方法开始。

jQuery 定义了一组 Ajax 速记方法,它们是核心 Ajax 函数的方便包装器,允许您轻松执行常见的 Ajax 任务。在接下来的小节中,我将向您介绍使用 HTTP GET请求从服务器检索数据的简化方法。表 14-2 总结了这些方法。

表 14-2 。jQuery Ajax 速记方法

名字描述
$.get()使用 HTTP GET 方法执行 Ajax 请求
$.post()使用 HTTP POST 方法执行 Ajax 请求

(简要)理解异步任务

对于不熟悉 Ajax 的人来说,这里有一个异步请求的简单解释。这很重要,因为这些任务对 Ajax 来说是如此重要,以至于首字母缩写代表异步。大多数时候,你习惯于写同步代码。您定义了一个执行某些任务的语句块,然后等待浏览器执行它们。当执行完最后一条语句时,就知道任务已经执行了。在执行过程中,浏览器不允许用户以任何方式与内容进行交互。

当你执行一个异步任务时,你是在告诉浏览器你想在后台做一些事情。短语“在后台”有点笼统,但本质上你是在说,“在不阻止用户与文档交互的情况下做这件事,完成后告诉我。”在 Ajax 的情况下,您告诉浏览器与服务器通信,并在请求完成时通知您。这个通知是通过回调函数处理的。您给 jQuery 一个或多个函数,当任务完成时将调用这些函数。将有一个函数来处理一个成功的请求,也可能有其他函数来处理其他结果,比如错误。

异步请求的优点是,它们允许您创建一个丰富的 HTML 文档,可以使用来自服务器的响应无缝更新该文档,而不会中断用户的交互,也不会让用户在浏览器加载新文档时等待。

缺点是你必须仔细考虑你的代码。您无法预测异步请求何时完成,也无法对结果做出假设。此外,回调函数的使用往往会创建更复杂的代码,这可能会惩罚那些对请求的结果或及时性做出假设的粗心的程序员。

执行 Ajax GET 请求

首先,我将使用 Ajax 执行一个 HTTP GET请求来加载一个 HTML 片段,然后我将它添加到浏览器显示的 HTML 文档中。清单 14-1 显示了我将使用的示例文档。

清单 14-1 。该示例文档

<!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 type="text/javascript">
        $(document).ready(function() {
            // ...
*code will go here*...
        });
    </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>

这类似于我在前面章节中使用的例子,但是没有描述产品的元素,也没有生成产品的数据项或模板。相反,我创建了一个名为flowers.html的单独文件,放在示例文档的旁边(名为example.html,可以在 Apress 网站[ www.apress.com ]的源代码/下载区找到)。清单 14-2 显示了flowers.html的内容。

清单 14-2 。flowers.html 文件的内容

<div>
    <img src="aster.png"/><label for="aster">Aster:</label>
    <input name="aster" value="0" required />
</div>
<div>
    <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
    <input name="daffodil" value="0" required />
</div>
<div>
    <img src="rose.png"/><label for="rose">Rose:</label>
    <input name="rose" value="0" required />
</div>
<div>
    <img src="peony.png"/><label for="peony">Peony:</label>
    <input name="peony" value="0" required />
</div>
<div>
    <img src="primula.png"/><label for="primula">Primula:</label>
    <input name="primula" value="0" required />
</div>
<div>
    <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
    <input name="snowdrop" value="0" required />
</div>

这些是我在前面章节中使用的相同元素,除了它们没有被分配给行,并且我已经从div元素中移除了class属性。我做了这些更改,以便在您加载元素后,我可以向您展示如何使用它们。注意,这不是一个完整的 HTML 文档,只是一个片段——例如,没有htmlheadbody元素。目前,flowers.html文档与主示例完全分离,你可以在图 14-1 中看到这一点。

9781430263883_Fig14-01.jpg

图 14-1 。最初的示例文档

我将使用 jQuery 对 Ajax 的支持,把 HTML 片段放到主 HTML 文档中。这似乎是一件奇怪的事情,但是我正在模拟一种常见的情况,即不同的内容由不同的系统生成,需要拼接在一起以创建一个复杂的文档或 web 应用。为了简单起见,我在这个例子中只使用了一台服务器,但是很容易想象关于产品的信息来自其他地方。事实上,在后面的例子中,我引入Node.js是为了向您展示如何处理多个服务器。这都是以后的事。现在,让我们看看基本的 jQuery Ajax 支持,并用它来处理flowers.html文件。清单 14-3 展示了我是如何做到这一点的。

清单 14-3 。通过 HTML 片段使用 jQuery Ajax 支持

...
<script type="text/javascript">
    $(document).ready(function () {
        $.get("flowers.html", function (data) {
            var elems = $(data).filter("div").addClass("dcell");
            elems.slice(0, 3).appendTo("#row1");
            elems.slice(3).appendTo("#row2");
        });
    });
</script>
...

我使用了 jQuery get方法,并提供了两个参数。第一个参数是我想要加载的 URL。在本例中,我指定了flowers.html,它将被解释为一个相对于加载主文档的 URL 的 URL。

第二个参数是一个函数,如果请求成功,它将被调用。正如我在侧栏中提到的,Ajax 依靠回调函数来提供通知,因为请求是异步执行的。jQuery 将来自服务器响应的数据作为参数传递给函数。

当包含这个脚本的文档被加载到浏览器中时,script元素被执行,我的 jQuery 代码从服务器加载flowers.html。一旦加载了flowers.html文档,它包含的 HTML 片段就会被解析成 HTML 元素,然后添加到文档中。图 14-2 显示了结果。

9781430263883_Fig14-02.jpg

图 14-2 。使用 Ajax 的效果

好吧,我承认我最终得到了与元素或数据内联时相同的结果,但是我采取的方法值得探索,在接下来的部分中,我将深入探讨细节。

image 提示虽然我使用了get方法来加载 HTML,但是它可以用来从服务器获取任何类型的数据。

处理响应数据

传递给 success 函数的参数是服务器为响应我的请求而发回的数据。在这个例子中,我获取了flowers.html文件的内容,这是一个 HTML 片段。为了使它成为我可以在 jQuery 中使用的东西,我将数据传递给 jQuery $函数,以便它被解析成一个层次结构的HTMLElement对象,如清单 14-4 中的所示。

清单 14-4 。处理从服务器获得的数据

...
<script type="text/javascript">
    $(document).ready(function() {
        $.get("flowers.html", function(data) {
            var elems = $(data).filter("div").addClass("dcell");
            elems.slice(0, 3).appendTo("#row1");
            elems.slice(3).appendTo("#row2");
        });
    });
</script>
...

正如我之前提到的,我从div元素中省略了class属性。你可以看到我使用 jQuery addClass方法将它们添加回去。一旦数据被传递给$函数,我就可以像使用其他对象一样使用返回的jQuery对象。我使用sliceappendTo方法将元素添加到文档中,正如我在前面章节中所做的那样。

image 提示注意,我使用了filter方法来只选择从数据中生成的div元素。在解析数据时,jQuery 假设我在结构的flowers.html文件中的div元素之间添加的回车符是文本内容,并为它们创建文本元素。为了避免这种情况,您可以确保在您请求的文档中没有回车,或者使用filter方法来删除它。这类似于我在第十三章中遇到的数据模板问题。

使效果更容易看到

创建 Ajax 请求的语句被执行以响应ready事件(我在第五章中描述过),这使得很难想象使用 Ajax 与使用内联数据有什么不同,因为flowers.html文件的内容是自动加载和显示的。为了使区别更加明显,我在文档中添加了一个button,并处理它生成的click事件,这样 Ajax 请求只有在被点击时才会被执行。您可以在清单 14-5 中看到这些变化。

清单 14-5 。发出 Ajax 请求以响应按钮按下的

...
<script type="text/javascript">
    $(document).ready(function () {
        $("<button>Ajax</button>").appendTo("#buttonDiv").click(function (e) {
            $.get("flowers.html",
                function (data) {
                    var elems = $(data).filter("div").addClass("dcell");
                    elems.slice(0, 3).appendTo("#row1");
                    elems.slice(3).appendTo("#row2");
                });
            e.preventDefault();
        });
    });
</script>
...

现在flowers.html文档直到按钮被点击才被加载,并且每次点击它,额外的元素被添加到文档中,如图图 14-3 所示。

9781430263883_Fig14-03.jpg

图 14-3 。使用 Ajax 响应按钮按压

image 提示注意,我已经在传递给我的事件处理函数的Event对象上调用了preventDefault方法。由于button元素包含在form元素中,默认的动作是将表单提交给服务器。

获取其他类型的数据

您不仅限于对 HTML 使用get方法——您可以从服务器获得任何类型的数据。特别感兴趣的是 JavaScript Object Notation (JSON)数据,因为 jQuery 处理来自服务器的响应的方式很有帮助。当 Ajax 开始被广泛采用时,XML 被视为首选的数据格式,以至于 Ajax 中的 X 代表 XML。我不打算深入研究 XML 的细节,但是 XML 往往很冗长,难以阅读,并且生成和处理起来相对耗费时间和资源。

近年来,XML 已经在很大程度上被 JSON 所取代,JSON 是一种更简单的数据格式,并且易于在 JavaScript 代码中使用(顾名思义)。对于这个例子,我已经创建了一个名为mydata.json的文件,并将它保存在 web 服务器上的example.html文件旁边。清单 14-6 显示了mydata.json?? 的内容。

清单 14-6 。mydata.json 文件的内容

[{"name":"Aster","product":"aster","stock":"10","price":"2.99"},
 {"name":"Daffodil","product":"daffodil","stock":"12","price":"1.99"},
 {"name":"Rose","product":"rose","stock":"2","price":"4.99"},
 {"name":"Peony","product":"peony","stock":"0","price":"1.50"},
 {"name":"Primula","product":"primula","stock":"1","price":"3.12"},
 {"name":"Snowdrop","product":"snowdrop","stock":"15","price":"0.99"}]

该文件包含花卉产品的数据,如您所见,JSON 数据几乎与您在 JavaScript 代码中表示数据的方式相同。为了使用 Ajax 加载和处理这些数据,我可以再次使用get方法,如清单 14-7 中的所示。

清单 14-7 。使用 get 方法获取 JSON 数据

...
<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="{{stock}}"
            value="0" required />
    </div>
    {{/flowers}}
</script> `<script type="text/javascript">`
    `$(document).ready(function () {`
        `$("<button>Ajax</button>").appendTo("#buttonDiv").click(function (e) {`
            `$.get("mydata.json", function (data) {`
                **var tmplData = $("#flowerTmpl").template({flowers: data}).filter("*");**
                **tmplData.slice(0, 3).appendTo("#row1");**
                **tmplData.slice(3).appendTo("#row2");**
            `});`
            `e.preventDefault();`
        `});`
    `});`
`</script>`
`...`

在这个例子中,我请求 JSON 数据文件来响应button点击。从服务器检索的数据被传递给一个函数,就像 HTML 片段一样。我已经使用车把模板插件(在第十二章中描述)来处理数据并从中生成 HTML 元素,然后使用sliceappendTo`方法将元素插入到文档中。请注意,我不需要做任何事情来将 JSON 字符串转换成 JavaScript 对象:jQuery 自动为我完成了这项工作。

image 提示一些网络服务器(包括我在本书中使用的微软 IIS 的一些版本)如果不能识别文件扩展名或数据格式,就不会向浏览器返回内容。为了让这个例子与 IIS 一起工作,我必须在文件扩展名(.json)和 JSON 数据的 MIME 类型(application/json)之间添加一个新的映射。在我这样做之前,当请求mydata.json时,IIS 会返回 404—未找到错误。

提供数据以获取请求

将数据作为GET请求的一部分发送到服务器是可能的,这种请求是由get方法(以及我在本章后面描述的loadgetScriptgetJSON方法)发出的。要将数据作为 GET 请求的一部分发送,需要将一个数据对象传递给get方法,如清单 14-8 所示。

清单 14-8 。作为 GET 请求的一部分发送数据

...
<script type="text/javascript">
    $(document).ready(function () {

        var requestData = {
            country: "US",
            state: "New York"
        };

        $("<button>Ajax</button>").appendTo("#buttonDiv").click(function (e) {
            $.get("mydata.json",requestData, function (data) {
                var tmplData = $("#flowerTmpl").template({flowers: data}).filter("*");
                tmplData.slice(0, 3).appendTo("#row1");
                tmplData.slice(3).appendTo("#row2");
            });
            e.preventDefault();
        });
    });
</script>
...

您提供的数据将作为查询字符串追加到指定的 URL。对于本例,这意味着您请求以下内容:

http://www.jacquisflowershop.com/jquery/flowers.html?country=US&state=New+York

服务器可以使用您提供的数据来定制返回的内容。例如,不同的州可能有不同的花卉区。您将无法在浏览器中看到用于发出 Ajax 请求的 URL,但是您可以使用开发人员的工具(通常称为 F12 工具,因为它们是通过 F12 键访问的)来查看正在发出什么请求。对于 Google Chrome,按 F12,在出现的窗口中点击Network标签,点击 XHR 过滤器(XHR 指的是XmlHttpRequest对象,是 jQuery 用来发出 Ajax 请求的文档对象模型(DOM)对象)。图 14-4 说明了 Chrome 如何在例子中显示 Ajax 请求的细节。

9781430263883_Fig14-04.jpg

图 14-4 。使用 Google Chrome F12 工具检查 Ajax 请求

获取和发布:选择正确的

您可能想使用GET请求发送表单数据。小心点。根据经验,GET请求应该用于只读信息检索,而POST请求应该用于任何改变应用状态的操作。

在符合标准的术语中,GET请求是针对安全的交互(除了信息检索之外没有副作用),而POST请求是针对不安全的交互(做出决定或改变某事)。这些惯例是由万维网联盟(W3C)在www.w3.org/Provider/Style/URI制定的。

因此,您可以使用GET请求向服务器发送表单数据,但不能用于改变状态的操作。2005 年,当 Google web Accelerator 向公众发布时,许多 Web 开发人员经历了惨痛的教训。这个应用预取了每个页面链接的所有内容,这在 HTTP 中是合法的,因为GET请求应该是安全的。不幸的是,许多 web 开发人员忽略了 HTTP 约定,在他们的应用中放置了指向“删除项目”或“添加到购物车”的简单链接。混乱随之而来。

一家公司认为其内容管理系统是多次恶意攻击的目标,因为其所有内容都不断被删除。该公司后来发现,一个搜索引擎爬虫偶然发现了一个管理页面的 URL,并抓取了所有删除链接。

执行 Ajax POST 请求

现在,您已经看到了如何从服务器获取数据,我可以将注意力转向如何发送数据了,也就是说,如何将表单数据发送到服务器。再次,还有一个速记方法:post,让发布一个表单变得简单。但是在我演示使用post方法之前,我需要扩展formserver.js文件的代码,以便Node.js能够接收和处理我在示例中使用的 POST 请求。

准备 Node.js 以接收表单数据

我需要一个服务器脚本,它将使用 HTTP POST方法接收从浏览器发送的数据,对已经发送的数据执行一些简单的操作,并生成响应。清单 14-9 显示了我第一次在第十三章中使用的formserver.js文件的更新版本。

清单 14-9 。修改后的 formserver.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"])
    }

    function writeResponse(req, res, data, jsonp) {
        var total = 0;
        for (item in data) {
            if (item != "_" && data[item] > 0) {
                total += Number(data[item]);
            } else {
                delete data[item];
            }
        }
        data.total = total;
        jsonData = JSON.stringify(data);
        if (jsonp) {
            jsonData = jsonp + "(" + jsonData + ")";
        }

        res.writeHead(200, "OK", {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*"
        });
        res.write(jsonData);
        res.end();
    }

}).listen(port);
console.log("Ready on port " + port);

image 提示获得这个脚本最简单的方法是下载本书附带的源代码,这个源代码可以在 Apress 网站www.apress.com上免费获得。我在第一章的中包含了获取Node.js的细节。

和以前一样,我通过在命令提示符下输入以下命令来运行脚本:

node.exe formserver.js

修改后的Node.js脚本处理浏览器发送的数据并创建 JSON 响应。我本来可以从这个脚本返回 HTML,但是 JSON 更紧凑,而且通常更容易使用。我返回的 JSON 对象是一个简单的对象,它包含用户选择的产品的总数,以及每个产品的指定值。例如,如果我选择了一朵紫苑、两朵水仙花和三朵玫瑰,那么由Node.js脚本返回的 JSON 响应将如下所示:

{"aster":"1","daffodil":"2","rose":"2","total":5}

我之前展示的 JSON 字符串表示一个对象数组,但是这个服务器脚本只返回一个对象,其属性对应于所选的花。total属性包含单个选择的总和。我知道这不是服务器可以执行的最有价值的活动,但是我想把重点放在使用 Ajax 而不是服务器端开发上。

理解跨来源的 Ajax 请求

如果您查看新的formserver.js脚本,您会看到当我向浏览器写响应时,我设置了一个 HTTP 头,如下所示:

Access-Control-Allow-Origin: http://www.jacquisflowershop.com

默认情况下,浏览器限制脚本在与包含它们的文档相同的内发出 Ajax 请求。来源是 URL 的协议、主机名和端口组成部分的组合。如果两个 URL 具有相同的协议、主机名和端口,那么它们在同一个源中。如果这三种成分中的任何一种不同,那么它们的来源就不同。

image 提示该策略旨在降低跨站点脚本 (CSS)攻击的风险,在这种攻击中,浏览器(或用户)被诱骗执行恶意脚本。CSS 攻击超出了本书的范围,但是在http://en.wikipedia.org/wiki/Cross-site_scripting有一篇有用的维基百科文章提供了关于这个主题的很好的介绍。

表 14-3 显示了与主示例文档的 URLwww.jacquisflowershop.com/jquery/example.html相比,URL 的变化如何影响原点。

表 14-3 。比较 URL

统一资源定位器原产地比较
http://www.jacquisflowershop.com/apps/mydoc.html相同的起源
https://www.jacquisflowershop.com/apps/mydoc.html出身不同;协议不同
http://www.jacquisflowershop.com:81/apps/mydoc.html出身不同;端口不同
http://node.jacquisflowershop.com/order出身不同;主机不同

在我的配置中,我有两台服务器。www.jacquisflowershop.com处理静态内容,node.jacquisflowershop.com运行Node.js。从表中可以看出,第一台服务器上的文档与第二台服务器上的文档有不同的来源。当你想从一个原点向另一个原点发出请求时,它被称为跨原点请求

这个政策的问题在于它是一个全面的禁令;没有跨来源请求。这导致使用一些丑陋的伎俩来欺骗浏览器发出违反策略的请求。幸运的是,现在有了一种合法的跨来源请求方式,在跨来源资源共享 (CORS) 规范中定义了这种方式。我只想简单地描述一下 CORS。有关完整的细节,请参见www.w3.org/TR/cors处的完整 CORS 标准。

image 提示CORS 规范相当新。当前一代的浏览器支持它,但是旧的浏览器会简单地忽略跨来源请求。一种更成熟的方法是使用 JSONP,我在“使用 JSONP”一节中对此进行了描述

CORS 的工作方式是,浏览器联系第二个服务器(在本例中是Node.js服务器),并在请求中包含一个Origin头。这个头的值是导致发出请求的文档的来源。

如果服务器识别出来源并希望允许浏览器进行跨来源请求,那么它会添加Access-Control-Allow-Origin头,设置值以匹配来自请求的Origin头。如果响应不包含这个头,那么浏览器会丢弃该响应。

image 提示支持 CORS 意味着浏览器在联系服务器并获得响应报头后,必须应用跨来源安全策略,这意味着即使响应因所需报头丢失或指定了不同的域而被丢弃,仍会发出请求。这与不实现 CORS 的浏览器不同,浏览器只是阻止请求,从不联系服务器。

formserver.js脚本中,我将Access-Control-Allow-Origin头设置为我的可信来源http://www.jacquisflowershop.com,但是您可以很容易地在请求中使用Origin头的值来遵循更复杂的决策过程。您还可以将Access-Control-Allow-Origin报头设置为星号(*,这意味着来自任何来源的跨来源请求都将被允许。这对于测试来说是没问题的,但是在生产应用中使用这种设置之前,您应该仔细考虑安全问题。

使用 post 方法提交表单数据

所以,现在我已经准备好了服务器并理解了 CORS,我可以使用post方法向服务器发送表单数据,如清单 14-10 所示。

清单 14-10 。用 post 方法发送数据

<!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="{{stock}}"
                value="0" required />
        </div>
        {{/flowers}}
    </script>
    <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 () {

            $.get("flowers.html", function (data) {
                var elems = $(data).filter("div").addClass("dcell");
                elems.slice(0, 3).appendTo("#row1");
                elems.slice(3).appendTo("#row2");
            });

            $("button").click(function (e) {
                var formData = $("form").serialize();
                $.post("[`node.jacquisflowershop.com/order`](http://node.jacquisflowershop.com/order)",
                    formData, 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>
</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>

这个例子看起来比实际更复杂。我首先使用getJSON方法获得包含花卉产品细节的mydata.json文件,然后使用数据模板生成元素并将它们添加到文档中。这给了我你开始了解和喜爱的起点,如图图 14-5 所示。您可以看到我已经在input元素中输入了一些值:12 朵紫苑、20 朵水仙花、4 朵报春花和 4 朵雪花莲。

9781430263883_Fig14-05.jpg

图 14-5 。向服务器发送数据的起点

我使用click方法来注册一个函数,当点击button元素时将调用这个函数,如下所示:

...
$("button").click(function (e) {
    var formData = $("form").serialize();
    $.post("http://node.jacquisflowershop.com/order", formData, processServerResponse);
    e.preventDefault();
});
...

我做的第一件事是在form元素上调用serialize方法。这是一个有用的方法,它遍历所有的form元素,并创建一个 URL 编码的字符串,可以发送给服务器。

image 提示注意,我在Event对象上调用了preventDefault方法,该对象被传递给我的click事件的处理函数。我需要这样做来阻止浏览器以常规方式发送表单——也就是说,通过发送数据并将响应作为单独的 HTML 文档加载。

对于我输入到input元素中的值,serialize方法生成一个字符串,如下所示:

aster=12&daffodil=20&rose=0&peony=0&primula=4&snowdrop=0

我使用serialize方法,因为post方法以 URL 编码的格式发送数据(尽管这可以通过使用ajaxSetup全局事件处理程序方法来改变,我在第十五章的中描述过)。一旦我从输入元素获得了data,我就调用post方法来发起 Ajax 请求。

post方法的参数是我要将数据发送到的 URL(它不需要与由form元素的action属性指定的 URL 相同)、我要发送的数据以及请求成功时要调用的函数。在本例中,我从服务器获取响应,并将其传递给processServerResponse函数,其定义如下:

...
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");
}
...

我隐藏了 CSS 布局中的所有单元格级别的div元素(它们是dcell类的成员),然后显示那些与来自服务器的 JSON 对象中的属性相对应的元素。我还使用一个新的数据模板来生成所选项目总数的显示。这些都是您可以在客户端执行的活动,但是这里的要点是您是通过 Ajax POST请求获得数据的。你可以在图 14-6 中看到结果。

9781430263883_Fig14-06.jpg

图 14-6 。处理从 Ajax POST 请求返回的数据的效果

您可以看到向服务器提交表单数据是多么容易(当然,处理响应是多么容易,特别是如果是 JSON 的话)。

image 提示如果您没有得到图中所示的响应,那么可能的原因是您的CORS头没有在Node.js脚本中设置到正确的域。作为一个快速测试,将它设置为*,看看会发生什么。

使用 post 方法发送其他数据

虽然post方法通常用于提交表单数据,但它实际上可以向服务器发送任何数据。我只是创建一个包含您的数据的对象,调用serialize方法来正确格式化数据,然后将它传递给post方法。

如果您在不使用表单的情况下从用户那里收集数据,或者如果您想要对包含在POST请求中的form元素进行选择,这可能是一种有用的技术。清单 14-11 展示了如何以这种方式使用post方法。

清单 14-11 。使用 post 方法向服务器发送非表单数据

...
<script type="text/javascript">
    $(document).ready(function () {

        $("button").click(function (e) {
            var requestData = {
                apples: 2,
                oranges: 10
            };

            $.post("http://node.jacquisflowershop.com/order", requestData,
                function (responseData) {
                    console.log(JSON.stringify(responseData));
                })
            e.preventDefault();
        })
    });
</script>
...

在这个脚本中,我创建了一个对象并显式定义了属性。我将这个对象传递给post方法,并使用console.log方法写出来自服务器的响应。(formserver.js脚本并不真正关心它从浏览器获得什么样的数据;它会尝试将这些值相加并生成一个总数。)该脚本会产生以下控制台输出:

{"apples":"2","oranges":"10","total":12}

image 提示来自服务器的 JSON 响应被 jQuery 自动转换成 JavaScript 对象。我使用了JSON.stringify方法(大多数浏览器都支持)将它转换回字符串,这样我就可以在控制台上显示它。

指定预期的数据类型

当您使用getpost方法时,jQuery 必须计算出服务器在响应您的请求时发送回哪种数据。它可以是从 HTML 到 JavaScript 文件的任何东西。为此,jQuery 依赖于服务器在响应中提供的信息,尤其是Content-Type头。在大多数情况下,这很好,但有时 jQuery 需要一点帮助。这通常是因为服务器为响应中的数据指定了错误的 MIME 类型。

您可以覆盖服务器提供的信息,并通过向getpost方法传递一个额外的参数来告诉 jQuery 您需要什么数据。该参数可以是下列值之一:

  • xml
  • json
  • jsonp
  • script
  • html
  • text

清单 14-12 展示了如何为get方法指定期望的数据类型。

清单 14-12 。指定预期的数据类型

...
<script type="text/javascript">
    $(document).ready(function () {
        $.get("mydata.json", function (responseData) {
            console.log(JSON.stringify(responseData));
        },"json");
    });
</script>
...

您将数据类型指定为速记方法的最后一个参数。在这个例子中,我已经告诉 jQuery 我正在等待 JSON 数据。服务器说内容类型是什么并不重要:jQuery 会将响应视为 JSON。此示例产生以下控制台输出:

[{"name":"Aster","product":"aster","stock":"10","price":"2.99"},
 {"name":"Daffodil","product":"daffodil","stock":"12","price":"1.99"},
 {"name":"Rose","product":"rose","stock":"2","price":"4.99"},
 {"name":"Peony","product":"peony","stock":"0","price":"1.50"},
 {"name":"Primula","product":"primula","stock":"1","price":"3.12"},
 {"name":"Snowdrop","product":"snowdrop","stock":"15","price":"0.99"}]

这与我放入mydata.json文件的内容相同,当然,这也是您所期望的。指定数据类型的问题是您必须是正确的。如果数据实际上是不同的类型,那么你可能会有一些问题,如清单 14-13 所示。

清单 14-13 。指定了错误的数据类型

...
<script type="text/javascript">
    $(document).ready(function () {

        $.get("flowers.html", function (responseData) {
            console.log(JSON.stringify(responseData));
        },"json");
    });
</script>
...

在这个例子中,我请求了一个包含 HTML 的文件,但是告诉 jQuery 应该将其视为 JSON。这里的问题是,在处理 JSON 时,jQuery 会自动从数据中创建一个 JavaScript 对象,这是它用 HTML 做不到的。

image 提示我会在第十五章中向你展示如何检测 Ajax 错误。

避免最常见的 Ajax 陷阱

在继续之前,我想向您展示 web 程序员使用 Ajax 最常见的问题,就是将异步请求视为同步请求。清单 14-14 给出了一个问题的例子。

清单 14-14 。一个常见的 Ajax 错误

...
<script type="text/javascript">
    $(document).ready(function () {

        var elems;

        $.get("flowers.html", function (data) {
            elems = $(data).filter("div").addClass("dcell");
        });

        elems.slice(0, 3).appendTo("#row1");
        elems.slice(3).appendTo("#row2");
    });
</script>
...

在这个例子中,我定义了一个名为elems的变量,Ajax 回调函数使用它来分配服务器请求的结果。我使用sliceappendTo方法将从服务器获得的元素添加到文档中。如果运行此示例,您将看到没有元素添加到文档中,并且根据您的浏览器,您将看到控制台上显示一条错误消息。以下是谷歌浏览器显示的信息:

Uncaught TypeError: Cannot call method 'slice' of undefined

这里的问题是,script元素中的语句没有按照编写的顺序执行。示例中的代码假设将出现以下序列:

  1. 定义elems变量。
  2. 从服务器获取数据,并将其赋给elems变量。
  3. elems变量中的元素切片并添加到文档中。

实际情况是这样的。

  1. 定义elems变量。
  2. 启动对服务器的异步请求。
  3. elems变量中的元素切片并添加到文档中。

在未来的某个时刻,这种情况会发生。

  1. 从服务器接收请求。
  2. 处理数据并将其分配给elems变量。

简而言之,我得到错误消息,因为我在一个不包含任何元素的变量上调用了slice方法。这个错误最糟糕的地方在于,有时代码实际上是有效的。这是因为 Ajax 响应可以完成得如此之快,以至于在我开始处理数据之前,变量就已经包含了数据(当浏览器缓存了数据,或者在启动 Ajax 请求和尝试操作数据之间执行一些复杂的操作时,通常会出现这种情况)。现在,您知道从代码中是否看到这种行为,应该寻找什么了。

使用特定类型的便利方法

jQuery 提供了三种方便的方法,使得处理特定类型的数据变得更加容易。表 14-4 总结了这些方法,这些方法将在随后的章节中演示。

表 14-4 。jQuery Ajax 特定类型的便利方法

名字描述
load()加载 HTML 元素并将它们插入到调用该方法的jQuery对象的元素中
$.getScript()获取并执行 JavaScript 代码
$.getJSON()获取 JSON 数据

获取 HTML 片段

load方法将获取 HTML 数据,这允许您请求 HTML 片段,处理响应以创建一组元素,并在一个步骤中将这些元素插入到文档中。清单 14-15 展示了如何使用load方法。

清单 14-15 。使用加载简写方法

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#row1").load("flowers.html");
    });
</script>
...

对文档中想要插入新元素的元素调用load方法,并将 URL 作为方法参数传递。如果请求成功,服务器的响应包含有效的 HTML,那么元素将被插入到指定的位置,如图 14-7 所示。

9781430263883_Fig14-07.jpg

图 14-7 。使用 load 方法向文档添加元素

来自flower.html文件的元素已经全部添加到文档中,但是因为它们缺少class属性,所以没有正确地添加到主文档使用的 CSS 表格布局中。由于这个原因,当所有的元素都被插入到一个单独的位置,并且在添加之前不需要修改它们的时候,load方法是最有用的。

操作由 load 方法添加的元素

load方法返回一个jQuery对象,该对象包含加载的 HTML 内容将要插入的元素。关键短语是将是,因为load方法使用异步请求从服务器获取 HTML。这意味着如果您想要操作由load方法添加到 DOM 中的元素,您必须小心,因为普通的 jQuery 技术不起作用。清单 14-16 显示了我在项目使用load方法时看到的最常见的问题。

清单 14-16 。load 方法最常见的问题代码

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#row1").load("flowers.html").children().addClass("dcell");
    });
</script>
...

代码的目的很明显:将flowers.html文件的内容加载到row1元素中,选择新添加的元素,并将它们添加到dcell类中(这将使它们作为我的 CSS 表格布局的一部分进行水平布局)。

但是如果你运行这个例子,你会发现与图 14-7 中的结果没有变化。这是因为load方法关闭并异步请求flowers.html文件,让 jQuery 自由地继续执行方法调用。因此,在 Ajax 请求完成并将新元素添加到文档之前,选择并修改了row1元素的子元素。

为了解决这个问题,load方法有一个可选参数,允许指定回调函数。在 Ajax 元素被添加到文档之前,回调函数不会被调用,这确保了我可以正确地对我的操作进行排序,如清单 14-17 所示。

清单 14-17 。使用 load 方法的回调参数

...
<script type="text/javascript">
    $(document).ready(function () {
        var targetElems = $("#row1");
        targetElems.load("flowers.html", function () {
            targetElems.children().addClass("dcell");
        });
    });
</script>
...

效果是直到flowers.html文件的内容被添加到文档中后,才执行对childrenaddClass方法的调用,产生如图 14-8 中所示的效果。

9781430263883_Fig14-08.jpg

图 14-8 。使用回调函数来操作加载方法添加的元素

获取和执行脚本

getScript方法加载一个 JavaScript 文件并执行其中包含的语句。为了演示这个方法,我创建了一个名为myscript.js的文件,并把它和example.html一起保存在我的 web 服务器上。清单 14-18 显示了这个文件的内容。

清单 14-18 。myscript.js 文件的内容

var flowers = [
    ["Aster", "Daffodil", "Rose"],
    ["Peony", "Primula", "Snowdrop"],
    ["Carnation", "Lily", "Orchid"]
]

$("<div id=row3 class=drow/>").appendTo("div.dtable");

var fTemplate = $("<div class=dcell><img/><label/><input/></div>");

for (var row = 0; row < flowers.length; row++) {
    var fNames = flowers[row];

    for (var i = 0; i < fNames.length; i++) {
        fTemplate.clone().appendTo("#row" + (row + 1)).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})
    }
}

这些语句生成三行描述花的元素。我已经使用循环生成了这些元素,所以我不必参与定义模板(虽然,一般来说,我更愿意使用数据模板,如第十二章中所述)。清单 14-19 展示了使用getScript方法获取并执行myscript.js文件的内容。

清单 14-19 。使用 getScript 方法

...
<script type="text/javascript">
    $(document).ready(function () {
        $.getScript("myscript.js");
    });
</script>
...

当 DOM 准备好时,调用getScript方法。执行myscript.js文件产生三行 flowers 元素,如图图 14-9 所示。

9781430263883_Fig14-09.jpg

图 14-9 。使用 getScript 方法加载并执行一个 JavaScript 文件

在处理这样的脚本时,要意识到的最重要的事情是,在您发起 Ajax 请求和正在执行的script语句之间,文档的状态可能会发生变化。清单 14-20 包含了一个来自主文档的脚本,它使用了getScript方法,但是也在 Ajax 请求完成之前修改了 DOM。

清单 14-20 。用 getScript 方法请求和执行脚本

...
<script type="text/javascript">
    $(document).ready(function () {
        $.getScript("myscript.js");
        $("#row2").remove();
    });
</script>
...

image 提示getScript方法可以用于任何脚本文件,但我发现它特别适用于加载和执行对 web 应用功能不重要的脚本,如跟踪器或地理定位脚本。用户并不关心我是否能够准确地定位他的位置来统计我的站点,但是他关心的是什么时候加载和执行脚本会让他等待。通过使用getScript方法,我可以得到我需要的信息,而不会让它变得令人讨厌。需要说明的是,我并不是建议你做任何对用户隐藏的事情,只是建议你推迟加载和执行合法的功能,因为用户不太可能把这些功能看得比他的时间更重要。

在这个例子中,我用getScript方法启动 Ajax 请求,然后调用remove方法从文档中删除row2元素。这个元素被myscript.js文件用来插入一些新元素。

这些本应添加到row2元素的元素被悄悄地丢弃了,因为row2 ID 的选择器与文档中的任何内容都不匹配。你可以在图 14-10 中看到结果。根据具体情况,您可以将这看作是一种健壮的设计,它在面对文档更改时尽了最大努力,或者是一种悄悄地处理元素的烦恼。不管怎样,不要对外部 JavaScript 文件中的文档状态做太多假设是有好处的。

9781430263883_Fig14-10.jpg

图 14-10 。Ajax 请求期间文档更改的效果

获取 JSON 数据

getJSON方法从服务器获取 JSON 数据并解析它以创建 JavaScript 对象。这可能是三种便利方法中最没用的,因为它并不比基本的get方法对数据做更多的事情。清单 14-21 展示了getJSON方法的使用。

清单 14-21 。使用 getJSON 方法

...
<script type="text/javascript">
    $(document).ready(function () {
        $.getJSON("mydata.json", function (data) {
            var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
            tmplElems.slice(0, 3).appendTo("#row1");
            tmplElems.slice(3).appendTo("#row2");
        });
    });
</script>
...

从服务器检索到的 JSON 数据被传递给一个回调函数,就像我在本章前面展示的get方法一样。我使用了一个数据模板(在第十二章的中描述)来处理数据并从中生成 HTML 元素,然后使用sliceappendTo方法将元素插入到文档中。

image 提示注意,您被传递了一个 JavaScript 对象作为函数的参数。您不需要做任何事情来将 JSON 格式转换成对象,因为 jQuery 会帮您完成这项工作。

使用 JSONP

JSONP 是 CORS 的替代方案,它解决了 Ajax 请求的同源限制。它依赖于这样一个事实,即浏览器将允许您从任何服务器加载 JavaScript 代码,这就是当您指定一个src属性时script元素的工作方式。首先,在文档中定义一个处理数据的函数,如下所示:

...
function processJSONP(data) {
    //...
*do something with the data*...
}
...

然后向服务器发出请求,其中查询字符串包含表单数据和一个callback属性,该属性设置为您刚刚定义的函数的名称,如下所示:

http://node.jacquisflowershop.com/order? callback=processJSONP&aster=1
    &daffodil=2&rose=2&peony=0&primula=0&snowdrop=0

服务器需要理解 JSONP 是如何工作的,它像往常一样生成 JSON 数据,然后创建一个 JavaScript 语句,调用您创建的函数,并将数据作为参数传入,如下所示:

processJSONP({"aster":"1","daffodil":"2","rose":"2","total":5})

服务器还将响应的内容类型设置为text/javascript,告知浏览器收到了一些 JavaScript 语句,应该执行这些语句。这相当于调用您之前定义的方法,传入服务器发送的数据。通过这种方式,您可以巧妙地避开相同域的问题,而不用使用 CORS。

image 警告跨产地请求受到限制是有原因的。不要随便用 JSONP。它会产生一些严重的安全问题。

jQuery 对 JSONP 有方便的支持。您所要做的就是使用getJSON方法,并在查询字符串中指定一个包含callback=?的 URL。jQuery 创建一个具有随机名称的函数,并在与服务器通信时使用它,这意味着您根本不需要修改代码。清单 14-22 演示了如何发出一个 JSONP 请求。

清单 14-22 。使用 getJSON 方法发出 JSONP 请求

...
<script type="text/javascript">
    $(document).ready(function () {
        $.getJSON("mydata.json", function (data) {
            var tmplElems = $("#flowerTmpl").template({ flowers: data }).filter("*");
            tmplElems.slice(0, 3).appendTo("#row1");
            tmplElems.slice(3).appendTo("#row2");

        });

        $("button").click(function (e) {
            var formData = $("form").serialize();
            $.getJSON("[`node.jacquisflowershop.com/order?callback=?`](http://node.jacquisflowershop.com/order?callback)",
                   formData, 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>
...

使用 Ajax 表单插件

到目前为止,我一直使用 Ajax 的内置 jQuery 支持。正如我前面提到的,jQuery 的优势之一是易于扩展,可以添加新的功能以及由此带来的插件世界。为了结束这一章,我将简要描述一个有用的与表单相关的插件。

如果您只对使用 Ajax 向服务器发送表单数据感兴趣,那么您可能会喜欢使用 jQuery Form 插件,您可以从www.malsup.com/jquery/form获得该插件,我将它保存到一个名为jquery.form.js的文件中。jQuery 表单插件使得在表单上使用 Ajax 变得非常简单,如清单 14-23 所示。

清单 14-23 。使用 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>
    <script src="jquery.form.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="{{stock}}"
                value="0" required />
        </div>
        {{/flowers}}
    </script>
    <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 () {

            $.getScript("myscript.js");

            $("form").ajaxForm(function (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>
</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>

在这个例子中,我已经将jquery.form.js脚本文件添加到了文档中(这个文件包含在插件的下载中),并且在script元素中,调用了form元素上的ajaxForm方法。ajaxForm方法的参数是一个回调函数,这为我提供了对服务器响应的访问。对于基本的 Ajax 表单来说,这是一种简洁而简单的方法,事实上发送表单的 URL 来自于form元素本身。

这个插件做得更多,它甚至包括一些对基本表单验证的支持,但是如果你想要开始控制你的 Ajax 请求,那么我建议使用我在第十五章中描述的低级 Ajax 特性。但是对于快速和简单的情况,这个插件是方便和设计良好的。

摘要

在本章中,我向您介绍了 jQuery 为 Ajax 提供的简写和方便的方法。我已经向您展示了如何使用getpost方法进行异步 HTTP GETPOST请求,如何处理 JSON 数据,以及如何使用处理特定数据类型的便利方法。一路上,我向您展示了最常见的 Ajax 缺陷,解释了跨源请求,展示了如何处理这些请求,并简要介绍了一个 jQuery 插件,该插件使得在表单中使用 Ajax 变得更加容易。在下一章中,我将向您展示底层 API,尽管您会看到它并不是真正的底层,而且实际上使用起来很愉快。`