jQuery2-高级教程-全-

jQuery2 高级教程(全)

原文:Pro jQuery 2.0

协议:CC BY-NC-SA 4.0

一、将 jQuery 放入上下文

从本质上来说,jQuery 做了一些听起来很枯燥的事情:它允许你通过操作浏览器在处理 HTML 时创建的模型来修改网页的内容,这个过程被称为 DOM(文档对象模型)操作,我将在后面详细描述。如果您已经阅读了这本书,那么您可能已经使用另一个 JavaScript 库或内置的 web 浏览器 API(应用编程接口)完成了一些 DOM 操作,现在您想以更好的方式完成它。

jQuery 超越更好。这使得 DOM 操作成为一种乐趣,有时甚至是真正的快乐。jQuery 的工作方式非常优雅,它将一项可能非常枯燥的任务转化为简单易行的工作。一旦开始使用 jQuery,就没有回头路了。以下是我在项目中使用 jQuery 的主要原因。

  • jQuery 富于表现力。我可以用更多的代码做更多的工作。
  • jQuery 方法适用于多个元素。选择-迭代-修改的 DOM API 方法消失了,这意味着更少的for循环来迭代元素和更少的错误。
  • jQuery 处理浏览器之间的实现差异。我不用担心 Internet Explorer (IE)是否以古怪的方式支持某个特性,比如;我只需告诉 jQuery 我想要什么,它就能解决我的实现差异。
  • jQuery 是开源的。当我不明白一些事情是如何工作的,或者我没有得到我期望的结果时,我可以通读 JavaScript 代码,如果需要的话,进行修改。

jQuery 的天才之处在于,它将 web 开发中的一些主要工作变得简单、快速和容易。我不能要求更多了。当然,并不是一切都是完美的,有一两个粗糙的边缘,我会在进入细节时解释。但是即使偶尔会有瑕疵,我还是喜欢使用 jQuery,我希望您会发现它同样引人入胜,令人愉快。

了解 jQuery UI 和 jQuery Mobile

除了核心的 jQuery 库之外,我还介绍了 jQuery UIjQuery Mobile ,它们是构建在 jQuery 之上的用户界面(UI)库。jQuery UI 是一个通用的 UI 工具包,可以在任何设备上使用,jQuery Mobile 是为智能手机和平板电脑等支持触摸的设备设计的。

了解 jQuery 插件

jQuery 插件扩展了基本库的功能。有些插件非常好,应用非常广泛,我在本书中已经介绍过了。有很多可用的插件(尽管质量可能有所不同),因此如果您不喜欢我在本书中描述的插件,您可以确信有一种替代方法可用。

我需要知道什么?

在阅读本书之前,您应该熟悉 web 开发的基础知识,了解 HTML 和 CSS(级联样式表)的工作原理,并且,理想情况下,具备 JavaScript 的工作知识。如果你对其中的一些细节不太清楚,我在第二章第一章、第三章第三章和第四章第五章提供了 HTML、CSS 和 JavaScript 的刷新工具。但是,你不会找到关于 HTML 元素和 CSS 属性的全面参考。在一本关于 jQuery 的书中,没有足够的空间来涵盖整个 HTML。如果你想要 HTML 和 CSS 的完整参考,那么我推荐我的另一本书:HTML 5 权威指南,也是由 Apress 出版的。

这本书的结构是什么?

这本书分为六个部分,每个部分涵盖一系列相关的主题。

第一部分:做好准备

本书的第一部分提供了您需要为本书的其余部分做准备的信息。它包括本章和 HTML、CSS 和 JavaScript 的入门/刷新。在这一章的后面,我将描述你需要的软件,以便跟随。

第二部分:使用 jQuery

本书的第二部分向您介绍了 jQuery 库,从一个基本示例开始,逐步扩展到包括每个核心特性 :元素选择、DOM 操作、事件和效果。

第三部分:使用数据和 Ajax

本书的第三部分展示了 jQuery 如何使处理内联或远程数据成为可能。我将向您展示如何从数据中生成 HTML 内容,如何验证输入到 web 表单中的数据,以及如何使用 jQuery 执行异步操作,包括 Ajax。

第四部分:使用 jQuery UI

jQuery UI 是我在本书中描述的两个用户界面库之一。jQuery UI 构建于核心 jQuery 库之上并与之集成,允许您为 web 应用创建丰富且响应迅速的界面。

第五部分:使用 jQuery Mobile

jQuery Mobile 是我在本书中介绍的另一个用户界面库。jQuery Mobile 构建在 jQuery 之上,并结合了 jQuery UI 的一些基本功能,但已经针对创建智能手机和平板电脑界面进行了优化。jQuery Mobile 中可用的 UI 小部件较少,但那些受支持的小部件针对触摸交互和较小显示器上的显示进行了优化。

第六部分:高级功能

本书的最后一部分描述了一些不常用的 jQuery 和 jQuery UI 特性,这些特性在复杂的项目中可能会有所帮助。这些高级特性需要对 HTML、CSS 和 jQuery 本身有更好的理解。在阅读第三十六章时,一点异步编程的基础知识很有帮助。

这个版本有什么新内容?

从这本书的第一版开始,jQuery、jQuery UI 和 jQuery Mobile 都发生了实质性的变化。

核心 jQuery 有什么新特性?

核心 jQuery 库的 API 非常稳定。几年前,jQuery 团队开始列出他们打算进行的更改,这些更改随着 jQuery 1.9 版的发布而实现。其中一些变化相当大,我已经在本书第二部分的每一章中指出了这些变化。

好消息是,这些变化非常罕见,API 可能会在几年内保持稳定。这并不是说不会增加新的特性,但是你今天开发的代码在未来一段时间内不需要修改就可以继续工作。

坏消息是,jQuery 团队在发布 jQuery 1.9 时做了一些不寻常的事情——他们还发布了 jQuery 2.0,因此他们将开发分成并行发布的两个系列:jQuery 1 . x 和 jQuery 2.x。这两个发布系列具有相同的 API,但 jQuery 2.x 不支持 IE 的 6、7 和 8 版本。

IE 的旧版本因其对 HTML、CSS 和 JavaScript 的非标准方法而臭名昭著,删除所有对奇怪行为的检查和相关的变通办法使 jQuery 2.x 变得更小更快。

image 提示在撰写本文时,jQuery 的当前版本是 2.0.2 和 1.10.1。理解 jQuery 2.0.2 并不能取代 1.10.1 版是很重要的。它们都是最新版本,唯一的区别是 jQuery 1.10.1 保留了对 Internet Explorer 旧版本的支持。

如果您确定您的用户都不会安装 Internet Explorer 6、7 或 8,那么您应该使用 jQuery 2.x。如果情况不是这样,或者您不确定,那么您应该使用 jQuery 1.x。这些版本的 IE 仍然被广泛使用,尤其是在大公司中,您应该仔细考虑使用 2.x 行的影响。

在编程书籍的理想化世界中没有遗留用户,我将在本书的大部分内容中使用 jQuery 2.0.2 库——但是您可以替换任何版本的 jQuery 1.x 行(从版本 1.9 开始)并获得相同的结果,同时保留对旧版本 IE 的支持。

image 提示我在第四部分描述 jQuery Mobile 的时候用的是 jQuery 1.x。jQuery Mobile 往往落后于主要的 jQuery 版本,在我写这篇文章时,它只支持 jQuery 1.x。

jQuery UI 有什么新特性?

jQuery UI 也进行了更新。使用现有用户界面小部件的 API 已经更新,更加一致,并且与支撑它们的 HTML 元素更加紧密地配合,并且添加了一些新的小部件。在本书的整个第三部分中,您会发现我在每章的开头都展示了重要的变化,就像我在第二部分中展示 jQuery 本身的变化一样。

jQuery Mobile 有什么新特性?

自从这本书的前一版以来,jQuery Mobile 已经成熟了很多。API 已经标准化,添加了新的小部件,整体开发人员体验与 jQuery 和 jQuery UI 更加一致。为了反映这种成熟,我完全重写了第四部分,使其与本书的其余部分保持一致。还有更多的示例、参考表和特定功能的演示。

还有什么新鲜的?

从第十二章开始,我使用模板从数据中生成 HTML 元素。这是一项重要的技术,我经常使用。我在以前版本中使用的库已经到了它的生命的尽头,我已经选择了一个替代品。新的库没有直接集成到 jQuery 中,所以在第十二章中,我提供了一个自定义插件,使得使用我选择的模板库更容易与 jQuery 一起使用。第十二章之后的所有例子都被修改以使用新的库。

我改变了用于测试移动应用的工具集,更喜欢使用基于云的测试服务,而不是维护自己的模拟器。我在第二十七章中解释了我这样做的原因。

有很多例子吗?

个载荷的例子。jQuery 的一个优点是几乎任何任务都可以用几种不同的方式执行,这允许您开发个人的 jQuery 风格。为了展示您可以采用的不同方法,我包含了许多不同的示例——事实上,数量如此之多,以至于我在某些章节中只包含了您正在使用的完整 HTML 文档一次,以便包含所有内容。每章的第一个例子是一个完整的 HTML 文档,如清单 1-1 所示。

清单 1-1 。完整的示例文档

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            $("img:odd").mouseenter(function (e) {
                $(this).css("opacity", 0.5);
            }).mouseout(function (e) {
                $(this).css("opacity", 1.0);
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div id="row2" class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

该列表摘自第五章。不要担心它做什么;请注意,每章的第一个例子是一个完整的 HTML 文档,类似于清单 1-1 中所示。几乎所有的例子都基于相同的基本 HTML 文档,显示了一个简单的花店。这不是最激动人心的例子,但它是独立的,包含了我们在使用 jQuery 时感兴趣的所有内容。

对于第二个和随后的示例,我只向您展示发生变化的元素。这通常只是script元素,也就是 jQuery 代码所在的位置。您可以发现部分清单,因为它以省略号(...)开始和结束,如清单 1-2 所示。

清单 1-2 。部分列表

...
<script type="text/javascript">
    jQuery(document).ready(function () {
        jQuery("img:odd").mouseenter(function(e) {
           jQuery(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           jQuery(this).css("opacity", 1.0);
        });
    });
</script>
...

清单 1-2 是第五章中的后续清单。您可以看到只出现了script元素,我突出显示了一些语句。这就是我如何将您的注意力吸引到示例中展示我正在使用的 jQuery 特性的部分。在这样的部分清单中,只有我显示的元素与本章开始时显示的完整文档有所不同。

我将本书中的例子非常集中在个人特征上。这是为了让您更好地了解 jQuery 的运行方式。但是在这样做的时候,你可能看不到不同的特性是如何组合在一起的,所以在本书的每一部分的结尾,都有一个简短的章节,在这个章节中,我重构了示例文档,将前面章节中的所有主题合并在一起,并给出了一个可能的联合视图。

从哪里可以获得示例代码?

你可以在 Apress 网站的源代码/下载区(www.apress.com)下载本书所有章节的所有例子。下载是免费的,它包括重新创建示例所需的所有支持资源,而不必输入它们(包括图像、JavaScript 库和 CSS 样式表)。您不必下载代码,但这是试验示例和将技术剪切并粘贴到您自己的项目中的最简单的方法。

image 提示尽管我只列出了章节中许多代码清单的变化,但是源代码下载中的每个例子都是一个完整的 HTML 文档,您可以直接将其加载到浏览器中。

这本书我需要什么软件?

为了遵循本书中的示例,您将需要各种软件,如以下部分所述。

获取 jQuery

您首先需要的是 jQuery 库,它可以从http://jquery.com获得。在网站的首页有一个下载按钮和一个选择生产或开发版本的选项,如图 1-1 所示。

9781430263883_Fig01-01.jpg

图 1-1 。下载 jQuery 库

您需要下载本书第 1-4 和第六部分的 jQuery 2.x 和第五部分的 jQuery 1.x。您将使用这本书的开发版本。我将在第五章中解释这些版本之间的区别,并向您展示如何建立 jQuery 库。

image 提示我在第十七章和第二十七章分别告诉你如何获取和安装 jQuery UI 和 jQuery Mobile 库。

获取 HTML 编辑器

web 开发最重要的工具之一是编辑器,您可以用它来创建 HTML 文档。HTML 只是文本,所以你可以使用一个非常基本的编辑器,但是有一些专用的包可以使开发更加流畅和简单,其中许多都是免费的。

当我写这本书的第一版时,我使用了活动状态下的 Komodo Edit。它是免费的;很简单;它对 HTML、JavaScript 和 jQuery 有很好的支持;还有 Windows、Mac 和 Linux 版本。详见http://activestate.com

不过最近换了微软的 Visual Studio。我的许多书都是关于微软的网络堆栈,我在 Windows 上写代码。Visual Studio 的最新版本对 HTML 编辑提供了出色的支持,可以在不依赖 Microsoft web stack 的情况下使用。我为这本书使用了 Visual Studio 2012 Express,这是免费提供的—参见http://www.microsoft.com/visualstudio。(也有付费版本——毕竟这是微软的——但是 jQuery 开发不需要额外的特性。)

作为替代,JsFiddle 是一个流行的在线编辑器,它支持使用 jQuery。我并不喜欢它(它的结构与我的开发习惯相冲突),但是它看起来确实非常灵活和强大。它是免费使用的,可在http://jsfiddle.net获得。

image 我推荐这本书里的几款产品,但只是因为它们是我使用和喜欢的产品。除了出版我所有书籍的出版公司 Apress 之外,我与 Active State、微软或任何其他公司都没有任何关系。我为我使用的每一个网络服务和开发工具支付全价;我没有得到任何特殊的支持或秘密访问开发团队,我收到的唯一的钱来自版税(我非常感谢您的购买——谢谢)。

获得网络浏览器

您需要一个 web 浏览器来查看 HTML 文档并测试 jQuery 和 JavaScript 代码。我喜欢谷歌浏览器:我发现它很快,我喜欢简单的用户界面,开发者工具也相当不错。每当你在这本书里看到一个截图(这是经常发生的),你看到的都会是谷歌 Chrome。

也就是说,你不必使用和我一样的浏览器,但是我建议你选择一个有好的开发工具的。Mozilla Firefox 通过 Firebug 扩展提供了一些优秀的 JavaScript 工具,您可以在http://getfirebug.com获得。

如果你不喜欢 Chrome 或 Firefox,那么你的下一个最佳选择是 Internet Explorer。很多网络程序员在道德上反对 IE,但版本 10 非常好,当 Chrome 以一种意想不到的方式运行时,我经常用它来快速检查是否正常。

获得网络服务器

如果您想重新创建本书中的示例,您将需要一个 web 服务器,以便浏览器有地方加载示例 HTML 文档和相关资源(如图像和 JavaScript 文件)。很多 web 服务器都是可用的,而且大部分都是开源和免费的。你使用哪个网络服务器并不重要。我在本书中使用了微软的互联网信息服务(IIS ),但这仅仅是因为我已经安装了一台 Windows 服务器并准备就绪。

获取 Node.js

从第三部分开始,除了常规的 web 服务器之外,您还将使用Node.jsNode.js目前非常流行,但我使用它的原因很简单,它是基于 JavaScript 的,所以你不必处理一个单独的 web 应用框架。你不会深入了解关于Node.js的任何细节,我会把它当作一个黑盒(尽管我会向你展示服务器脚本,如果你感兴趣的话,你可以看看服务器上发生了什么)。

可以从http://nodejs.org下载Node.js。有一个预编译的 Windows 二进制文件和源代码,您可以为其他平台构建。在这本书里,我使用的是 0.10.13 版本,在你读到这本书的时候,这个版本很可能已经被取代了,但是服务器脚本应该还能正常工作,没有任何问题。

设置和测试 Node.js

测试Node.js最简单的方法是使用一个简单的脚本。将清单 1-3 中的内容保存到一个名为NodeTest.js的文件中。我在与我的Node.js二进制文件相同的目录下完成了这个操作。

清单 1-3 。Node.js 测试脚本

var http = require('http');
var url = require('url');

http.createServer(function (req, res) {
    console.log("Request: " + req.method + " to " + req.url);

    res.writeHead(200, "OK");
    res.write("<h1>Hello</h1>Node.js is working");
    res.end();

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

这是一个简单的测试脚本,当它接收到一个 HTTP GET请求时返回一个 HTML 片段。

提示如果最后一句话没有完全说明白,不要担心。使用 jQuery 不需要知道 HTTP 和 web 服务器是如何工作的,我在第二章的中提供了一个 HTML 速成班。

要测试Node.js,运行二进制文件,将刚刚创建的文件指定为参数。对于我的 Windows 安装,我在控制台提示符下键入了以下内容:

node NodeTest.js

为了确保一切正常,导航到运行Node.js的机器上的端口 80。您应该会看到与图 1-2 非常相似的东西,表明一切都在预期之中。

9781430263883_Fig01-02.jpg

图 1-2 。测试 Node.js

我在不同于普通 web 服务器的机器上运行Node.js,这意味着使用端口 80 不会给我带来任何问题。如果您只有一台机器可用,那么在端口 80 上运行 web 服务器,并将Node.js脚本更改为使用另一个端口。我在清单 1-3 中突出显示了测试脚本中指定使用哪个端口的部分。

图像属性

在本书中,我在例子中使用了一组图像。感谢以下人员善意地允许使用他们的照片:霍里亚·瓦兰、大卫·肖特、盖沙博伊 500 、田中久约、梅尔维·埃斯凯利宁、花式速度女王、艾伦“克雷吉 3000 、克雷吉、诺索古德和梅拉路易丝

摘要

在这一章中,我概述了这本书的内容和结构,并列出 jQuery web 开发所需的软件,所有这些都可以免费获得。接下来的三章将更新你在 HTML、CSS 和 JavaScript 方面的基本技能。如果你熟悉这些主题,那么跳到第五章,我将在那里介绍 jQuery。

二、 HTML 优先

在本书中,我们将花大量的时间处理 HTML 文档。在这一章中,我列出了你需要的信息来理解我们在本书后面要做的事情。这不是一个 HTML 教程,而是我在后面章节所依赖的 HTML 关键特性的总结。

HTML 的最新版本被称为 HTML5 ,它本身就是一个话题。HTML5 有 100 多个元素,每个元素都有自己的用途和功能。也就是说,你只需要 HTML 的基础知识就可以理解 jQuery 是如何工作的,但是如果你想了解 HTML 的细节,那么我推荐我的另一本书:HTML 5权威指南,也是由 Apress 出版的。

介绍基本的 HTML 文档

最好的起点是查看 HTML 文档。从这样的文档中,你可以看到所有 HTML 文档遵循的基本结构和层次。清单 2-1 显示了一个简单的 HTML 文档。我在这一章中使用这份文档来介绍 HTML 的核心概念。

清单 2-1 。一个简单的 HTML 文档

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <style>
        h1 {
            width: 700px; border: thick double black; margin-left: auto;
            margin-right: auto; text-align: center; font-size: x-large; padding: .5em;
            color: darkgreen; background-image: url("border.png");
            background-size: contain; margin-top: 0;
        }
        .dtable {display: table;}
        .drow {display: table-row;}
        .dcell {display: table-cell; padding: 10px;}
        .dcell > * {vertical-align: middle}
        input {width: 2em; text-align: right; border: thin solid black; padding: 2px;}
        label {width: 5em;  padding-left: .5em;display: inline-block;}
        #buttonDiv {text-align: center;}
        #oblock {display: block; margin-left: auto; margin-right: auto; width: 700px;}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <div id="oblock">
            <div class="dtable">
                <div class="drow">
                    <div class="dcell">
                        <img src="aster.png"/><label for="aster">Aster:</label>
                        <input name="aster" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

这是一个简短的基本 HTML 文档,但是它包含了一些与 HTML 相关的最重要的特征。你可以在图 2-1 中看到该文档如何出现在浏览器中。

9781430263883_Fig02-01.jpg

图 2-1 。在浏览器中显示 HTML 文档示例

理解 HTML 元素的结构

HTML 的核心是元素,它告诉浏览器 HTML 文档的每个部分代表什么样的内容。以下是示例中的一个元素:

...
<h1>Jacqui's Flower Shop</h1>
...

这个元素有三个部分:开始标签,结束标签,内容,如图图 2-2 所示。

9781430263883_Fig02-02.jpg

图 2-2 。简单 HTML 元素的剖析

这个元素的名称(也称为标签名称或者仅仅是标签)是h1,它告诉浏览器标签之间的内容应该被当作一个顶级头。您可以通过将标签名称放在尖括号、<>字符中来创建开始标签。除了在左尖括号(<)后面添加一个/字符之外,您可以用类似的方式创建结束标记。

了解属性

您可以通过向元素添加属性来为浏览器提供附加信息。 清单 2-2 显示了示例文档中一个带有属性的元素。

清单 2-2 。定义属性

...
<label for="aster">Aster:</label>
...

这是一个label元素,它定义了一个名为for的属性。我强调了属性,以便更容易看到。属性总是被定义为开始标记的一部分。这个属性有一个 名称和一个 。名字是for,数值是aster。并非所有属性都需要值;仅仅定义它们就向浏览器发送了一个信号,表明您需要与该元素相关联的某种行为。清单 2-3 显示了一个具有这种属性的元素的例子。

清单 2-3 。定义不需要值的属性

...
<input name="snowdrop" value="0" required>
...

这个元素有三个属性。前两个值namevalue被赋值。(这可能会有点混乱。这些属性的名字是namevaluename属性的值是snowdrop,value属性的值是0。)第三个属性正好是required这个词。这是一个不需要值的属性的例子,尽管您可以通过将属性值设置为其名称(required="required")或使用空字符串(required="")来定义一个值。

id 和类属性

在这本书里有两个属性特别重要:属性的idclass。使用 jQuery 需要执行的最常见的任务之一是在文档中定位一个或多个元素,以便对它们执行某种操作。idclass属性对于在 HTML 文档中定位一个或多个元素非常有用。

使用 id 属性

使用id属性为文档中的元素定义一个惟一的标识符。对于id属性,不允许两个元素具有相同的值。清单 2-4 显示了一个使用了id属性的简单 HTML 文档。

清单 2-4 。使用 id 属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
</head>
<body>
    <h1 id="mainheader">Welcome to Jacqui's Flower Shop</h1>
    <h2 id="openinghours">We are open 10am-6pm, 7 days a week</h2>
    <h3 id="holidays">(closed on national holidays)</h3>
</body>
</html>

我已经在文档中的三个元素上定义了id属性。h1元素的id值为mainheader,h2元素的id值为openinghours,h3元素的id值为holidays。使用id值可以让您在文档中找到特定的元素。

使用类属性

属性任意地将元素关联在一起。许多元素可以被分配到同一个类,元素可以属于多个类,如清单 2-5 所示。

清单 2-5 。使用 class 属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
</head>
<body>
    <h1 id="mainheader" class="header">Welcome to Jacqui's Flower Shop</h1>
    <h2 class="header info">We are open 10am-6pm, 7 days a week</h2>
    <h3 class="info">(closed on national holidays)</h3>
</body>
</html>

在清单 2-5 中,h1元素属于header类,h2元素属于headerinfo类,h3元素属于info类。如您所见,您可以将一个元素添加到多个类中,只需用空格分隔类名。

了解元素内容

元素可以包含文本,但也可以包含其他元素。以下是包含其他元素的元素的示例:

...
<div class="dcell">
    <img src="rose.png"/>
    <label for="rose">Rose:</label>
    <input name="rose" value="0" required>
</div>
...

div元素包含另外三个元素:一个imglabel和一个input元素。您可以定义多个层次的 嵌套元素,而不仅仅是这里显示的一个层次。像这样嵌套元素是 HTML 中的一个关键概念,因为它将外部元素的重要性传递给了内部元素(这是我稍后要讨论的主题)。您可以混合文本内容和其他元素,如下所示:

...
<div class="dcell">
    Here is some text content
    <img src="rose.png"/>
    Here is some more text!
    <input name="rose" value="0" required>
</div>
...

了解无效元素

HTML 规范包括可能不包含内容的元素。这些被称为 void自闭元素,它们没有单独的结束标签。以下是 void 元素的示例:

...
<img src="rose.png"/>
...

在单个标记中定义了一个 void 元素,并在最后一个尖括号(>字符)前添加了一个/字符。严格来说,最后一个属性的最后一个字符和/字符之间应该有一个空格,如下:

...
<img src="rose.png" />
...

然而,浏览器在解释 HTML 时是宽容的,您可以省略空格字符。当元素引用外部资源时,通常使用 Void 元素。在这种情况下,img元素用于链接到名为rose.png的外部图像文件。

了解文档结构

有一些关键元素定义了任何 HTML 文档的基本结构:DOCTYPEhtmlheadbody元素。清单 2-6 显示了这些元素之间的关系,删除了其余的内容。

清单 2-6 。HTML 文档的基本结构

<!DOCTYPE html>
<html>
<head>
    ...*head content*...
</head>
<body>
    ...*body content*...
</body>
</html>

这些元素中的每一个在 HTML 文档中都扮演着特定的角色。DOCTYPE元素告诉浏览器这是一个 HTML 文档,更确切地说,这是一个 HTML5 文档。早期版本的 HTML 需要额外的信息。例如,下面是 HTML4 文档的DOCTYPE元素:

...
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd">
...

html元素表示包含 HTML 内容的文档区域。这个元素总是包含另外两个关键的结构元素:headbody。正如我在本章开始时所说的,我不打算讨论单个的 HTML 元素。它们太多了,描述 HTML5 完全花了我 HTML5 书 1000 多页。也就是说,我将提供我使用的元素的简要描述,以便您对文档的功能有一个好的了解。表 2-1 总结了来自清单 2-1 的示例文档中使用的元素,其中一些我将在本章稍后部分详细描述。

表 2-1 。示例文档中使用的 HTML 元素

元素 描述
DOCTYPE 指示文档中内容的类型
body 表示包含内容元素的文档区域(在“理解内容元素”一节中描述)
button 表示一个按钮;通常用于向服务器提交一个表单
div 通用元素;通常用于为文档添加结构,以用于演示目的
form 表示一个 HTML 表单,它允许您从用户那里收集数据,并将它们发送到服务器进行处理
h1 降级标题
head 表示文档中包含元数据的区域(在“了解元数据元素”一节中描述)
html 表示文档中包含 HTML 的区域(通常是整个文档)
img 降级图像
input 表示用于从用户处收集单个数据项的输入字段,通常作为 HTML 表单的一部分
script 表示将作为文档的一部分执行的脚本,通常是 JavaScript
style 表示 CSS 设置的区域;参见第三章
title 表示文档的标题;由浏览器用来设置用于显示文档内容的窗口或选项卡的标题

了解元数据元素

head元素包含文档的元数据——换句话说,一个或多个描述或操作文档内容的元素,但不直接由浏览器显示。示例文档在head部分包含三个元数据元素:titlescriptstyletitle元素是最基本的,这个元素的内容被浏览器用来设置窗口或标签的标题,所有的 HTML 文档都需要有一个title元素。其他两个元素对本书来说更重要,我将在接下来的章节中解释。

理解脚本元素

script元素允许您在代码中包含 JavaScript。一旦我开始深入介绍 jQuery,您就会经常看到这个元素。示例文档包含一个script元素,如清单 2-7 所示。

清单 2-7 。示例文档中的脚本元素

...
<script src="jquery-2.0.2.js" type="text/javascript"></script>
...

当您为script元素定义src属性时,您是在告诉浏览器您想要加载包含在另一个文件中的 JavaScript。在这种情况下,这是主 jQuery 库,浏览器将在文件jquery-2.0.2.js中找到它。一个 HTML 文档可以包含不止一个script元素,如果你愿意,可以在开始和结束标签之间包含 JavaScript 代码,如清单 2-8 所示。

清单 2-8 。使用 script 元素定义内联 JavaScript 代码

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function() {
           $('#mainheader').css("color", "red");
        });
    </script>
</head>
<body>
    <h1 id="mainheader" class="header">Welcome to Jacqui's Flower Shop</h1>
    <h2 class="header info">We are open 10am-6pm, 7 days a week</h2>
    <h3 class="info">(closed on national holidays)</h3>
</body>
</html>

这个例子有两个script元素。第一个是将 jQuery 库导入到文档中,第二个是使用一些基本 jQuery 功能的简单脚本。暂时不要担心第二个脚本会做什么。我将在第五章开始适当地进入 jQuery。在 HTML 文档中,script元素可以出现在headbody元素中。在本书中,我倾向于将脚本只放在head元素中,但这只是个人偏好的问题。

image 提示script元素的顺序很重要。您必须先导入 jQuery 库,然后才能使用它的特性。

了解样式元素

style元素是将级联样式表(CSS)属性引入文档的一种方式。简而言之,您可以使用 CSS 来管理文档在浏览器中显示给用户时的呈现方式。清单 2-9 显示了示例文档中的style元素及其内容。

清单 2-9 。使用样式元素

...
<style>
    h1 {
        width: 700px; border: thick double black; margin-left: auto;
        margin-right: auto; text-align: center; font-size: x-large; padding: .5em;
        color: darkgreen; background-image: url("border.png");
        background-size: contain; margin-top: 0;
    }
    .dtable {display: table;}
    .drow {display: table-row;}
    .dcell {display: table-cell; padding: 10px;}
    .dcell > * {vertical-align: middle}
    input {width: 2em; text-align: right; border: thin solid black; padding: 2px;}
    label {width: 5em;  padding-left: .5em;display: inline-block;}
    #buttonDiv {text-align: center;}
    #oblock {display: block; margin-left: auto; margin-right: auto; width: 700px;}
</style>
...

浏览器维护一组属性,这些属性的值用于控制每个元素的外观。style元素允许您选择元素并更改一个或多个属性的值。我会在第三章中更详细地讨论这个问题。

script元素一样,style元素也可以出现在headbody元素中,但是在本书中,你会发现我只将它们放在了head部分,就像示例文档中一样。这又是个人喜好问题;我喜欢把我的风格和我的内容分开。

理解内容元素

元素包含了 HTML 文档中的内容。这些是浏览器将向用户显示的元素,元数据元素,如scriptstyle,将对这些元素进行操作。

理解语义/表示差异

HTML5 的主要变化之一是哲学上的:元素的语义意义和元素对内容表示的影响之间的分离。这是一个明智的想法。您使用 HTML 元素为内容提供结构和含义,然后通过对元素应用 CSS 样式来控制内容的表示。并不是每一个 HTML 文档的消费者都需要显示它们(例如,因为 HTML 的一些消费者是自动化程序而不是浏览器),并且保持表示独立使得 HTML 更容易处理和自动提取含义。

这个概念是 HTML 的核心。您应用元素来表示您正在处理的内容类型。人们善于根据上下文推断意义。例如,你马上就明白了这一部分的标题从属于前面的标题,因为它是用较小的字体印刷的(也因为这是你在你读过的大多数非小说类书籍中看到的一种模式)。

计算机也不能推断任何地方的上下文,因此,每个 HTML 元素都有特定的含义。例如,article元素表示适合于联合的自包含内容,h1元素表示内容部分的标题。清单 2-10 显示了一个使用元素赋予结构和意义的示例文档。

清单 2-10 。使用 HTML 元素为内容添加结构和意义

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
</head>
<body>
    <article>
        <header>
            <hgroup>
                <h1>New Delivery Service</h1>
                <h2>Color and Beauty to Your Door</h2>
            </hgroup>
        </header>
        <section>
            We are pleased to announce that we are starting a home delivery service for
            your flower needs. We will deliver within a 20 mile radius of the store
            for free and $1/mile thereafter. All flowers are satisfaction-guaranteed and
            we offer free phone-based consultation.
        </section>
        <section>
            Our new service starts on <b>Wednesday</b> and there is a $10 discount
            for the first 50 customers.
        </section>
        <footer>
            <nav>
                More Information:
                <a href="http://jacquisflowershop.com">Learn More About Fruit</a>
            </nav>
        </footer>
    </article>
</body>
</html>

关于何时应用sectionarticle元素,没有硬性规定,但我建议你在内容中始终如一地应用它们。像sectionarticle这样的元素不向浏览器提供任何关于它们包含的内容应该如何显示的信息。这是语义/表示分歧的核心。对于大多数 HTML 元素,浏览器有一个样式约定,它决定了如果不使用 CSS 改变演示文稿,它们将如何显示,但其思想是您将自由地使用 CSS 来创建您的文档所需的演示文稿。这是你可以用style元素做的事情,jQuery 在script元素中很容易做到。

HTML4 中存在的一些元素是在没有将表示和意义分开的概念时创建的,这将我们置于一个奇怪的境地。这种奇怪现象的一个例子是b元素。在 HTML5 之前,b元素指示浏览器将开始和结束标签包含的内容显示为粗体文本。在 HTML5 中,元素不仅仅是表示性的,因此b元素的定义被修改如下:

b元素表示从其周围内容偏移的一段文本,没有传达任何额外的强调或重要性,并且传统的印刷呈现是粗体文本;例如,文档摘要中的关键字,或者评论中的产品名称。

—HTML:标记语言,w3c.org

这是一种冗长的说法,即b元素告诉浏览器将文本加粗。b元素没有语义意义;这是所有关于介绍。这个含糊其辞的定义告诉了我们一些关于 HTML5 的重要事情:我们正处于一个过渡时期。我们希望元素和它们的表示完全分离,但现实是我们也希望保持与无数使用早期 HTML 版本编写的文档的兼容性,所以我们不得不妥协。

理解表单和输入

示例文档主体中最有趣的元素之一是form元素。这是一种可以用来从用户那里收集数据的机制,这样就可以将数据发送到服务器。正如您将在第三部分中看到的,jQuery 对处理表单有很好的支持,包括直接在核心库中和在一些常用的插件中。清单 2-11 显示了示例文档中的body元素及其内容,其中包括form元素。

清单 2-11 。示例文档的内容元素

...
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <div id="oblock">
            <div class="dtable">
                <div class="drow">
                    <div class="dcell">
                        <img src="aster.png"/><label for="aster">Aster:</label>
                        <input name="aster" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
...

当有一个form元素时,通常可以在附近找到input元素。这是您用来从用户那里获取特定信息的元素。清单 2-12 展示了一个来自文档的input元素的例子。

清单 2-12 。使用输入元素

...
<input name="snowdrop" value="0" required>
...

这个input元素从用户那里收集一个名为snowdrop 的数据项的值,它的初始值为零。required属性告诉浏览器,除非用户为这个数据项提供了一个值,否则不能将表单发送到服务器。这是 HTML5 中的一个新特性,叫做表单验证 ,但是坦率地说,使用 jQuery 可以实现更好的验证,正如我在第三部分中演示的那样。

与表单密切相关的是button元素,它通常用于向服务器提交表单(也可以用于将表单重置为初始状态)。清单 2-13 显示了我在示例文档中定义的button元素。

清单 2-13 。使用按钮元素

...
<button type="submit">Place Order</button>
...

type属性设置为submit告诉浏览器当按钮被按下时我希望表单被提交。button元素的内容显示在浏览器的按钮控件内,如图 2-3 所示。

9781430263883_Fig02-03.jpg

图 2-3 。使用按钮元素的内容

了解结构元素

您会注意到在example文档的主体中有许多div元素。这是一个没有特定语义意义的元素,通常只是用来控制内容的布局。在示例文档的情况下,我使用div元素创建一个布局,这样div元素包含的元素就在一个网格中呈现给用户。布局通过包含在style元素中的一些 CSS 应用于div元素。我将在整本书中使用 CSS,我会在第三章给出一个快速入门。

借助外部资源了解元素

有些元素允许您将外部资源引入文档。一个很好的例子是img元素,您可以使用它向文档添加图像。在清单 2-1 中的示例文档中,我使用了img元素来显示出售的不同花卉的图片,如清单 2-14 所示。

清单 2-14 。使用 img 元素引用外部图像

...
<img src="snowdrop.png"/>
...

src属性用于指定图像。我用过图片snowdrop.png。这是一个相对 URL (统一资源定位器)的例子,这意味着浏览器将使用包含该元素的文档的 URL 来计算出我想要的图像的 URL。

相对 URL 的替代方法是一个绝对 URL (也称为完全限定 URL )。这是一个定义了所有基本组件的 URL,如图 2-4 所示。(我已经在图 2-4 中包括了端口,但是如果省略,那么浏览器将使用与方案相关的默认端口。对于http方案,这是端口80。)

9781430263883_Fig02-04.jpg

图 2-4 。URL 的基本结构

为每一个你想要的资源输入完全合格的 URL 是令人厌烦的,这就是为什么相对 URL 如此有用。当我为img元素的src属性指定一个值snowdrop.png时,我告诉浏览器它可以在获取包含img元素的文档的相同位置找到图像。表 2-2 显示了你可以使用的不同种类的相对 URL 和从它们创建的绝对 URL。这些都假设文档是从http://www.jacquisflowershop.com/jquery/example.html加载的。

表 2-2 。相对 URL 格式

相对 URL 等于
snowdrop.png http://www.jacquisflowershop.com/jquery/snowdrop.png
/snowdrop.png http://www.jacquisflowershop.com/snowdrop.png
/ http://www.jacquisflowershop.com/jquery/
//www.mydomain.com/index.html http://www.mydomain.com/index.html

表中的最后一个例子很少使用,因为它没有节省太多的输入,但是它可以用来确保使用与检索 HTML 文档相同的方案来请求资源。这避免了通过加密连接请求某些内容(使用https方案)和通过未加密连接请求其他内容(使用http方案)的问题。一些浏览器,尤其是 Internet Explorer 和 Google Chrome,不喜欢混合安全和不安全的内容,并且会在出现这种情况时警告用户。

image 注意您可以使用两个句点(..)相对于 web 服务器上包含主 HTML 文档的目录进行导航。我建议避免使用这种技术,尤其是因为作为一种安全预防措施,许多 web 服务器会拒绝包含这些字符的请求。

了解元素层次结构

HTML 文档中的元素形成了一个自然的层次结构。html元素包含body元素,后者包含内容元素,每个内容元素可以无限地包含其他元素

*当您想要导航文档时,理解这种层次结构是很重要的,无论是使用 CSS 应用样式(我在第三章中描述了这一点)还是使用 jQuery 在文档中查找元素(我将在第二部分中解释)。

层次结构中最重要的部分是元素之间的关系。为了帮助我描述这些关系,我在图 2-5 中描述了花店示例文档中一些元素的层次结构。

9781430263883_Fig02-05.jpg

图 2-5 。文档层次结构的一部分

图 2-5 只显示了文档中元素层次结构的一部分,所以你可以看到关系直接对应于一个元素可以包含另一个元素的方式。有各种各样的关系,如以下部分所述。

了解父子关系

例如,当一个元素包含另一个元素时,就存在父子关系。form元素是图中body元素的。相反,body元素是form元素的父元素。一个元素可以有多个子元素,但只能有一个父元素。在图 2-5 中,body元素有两个子元素(formh1元素),并且是它们的父元素。

父子关系仅存在于元素和直接包含在其中的元素之间。例如,div元素是form元素的子元素,但它们不是body元素的子元素。

孩子关系有一些变化。第一个子元素是文档中首先定义的子元素。例如,h1元素是body元素的第一个子元素。最后一个子元素是文档中定义的最后一个子元素。form元素是body元素的最后一个子元素。也可以参考nth-child,从第一个子元素开始,开始计数子元素,直到到达n(从1开始计数)。

理解祖孙关系

一个元素的后代是它的子元素,子元素的子元素,依此类推。事实上,任何直接或间接包含的元素都是后代。例如,body元素的后代是h1form和两个div元素,图 2-5 中显示的所有元素都是html元素的后代。

相反的关系是祖先,它们是元素的父元素、父元素的父元素等等。例如,对于form元素,后代是bodyhtml元素。两个div元素有相同的祖先集:formbodyhtml元素。

了解兄弟姐妹关系

兄弟元素是共享一个公共父元素的元素。在图 2-5 中,h1form元素是兄弟元素,因为它们共享body元素作为它们的父元素。当与兄弟姐妹一起工作时,我们倾向于参考下一个兄弟姐妹上一个兄弟姐妹。这些是在当前元素之前和之后定义的兄弟元素。并非所有元素都有上一个和下一个同级元素;第一个和最后一个子元素只有一个。

理解文档对象模型

当浏览器加载并处理一个 HTML 文档时,它会创建文档对象模型 (DOM)。DOM 是一种模型,其中 JavaScript 对象用于表示文档中的每个元素,DOM 是一种机制,通过它可以以编程方式处理 HTML 文档的内容。

image 注意原则上,DOM 可以用于浏览器想要实现的任何编程语言。实际上,JavaScript 主导着主流浏览器,所以我不打算区分作为抽象概念的 DOM 和作为相关 JavaScript 对象集合的 DOM。

您应该关心我在上一节中描述的元素之间的关系的原因之一是它们保存在 DOM 中。因此,您可以使用 JavaScript 遍历对象网络,以了解所表示的文档的性质和结构。

image 提示使用 DOM 意味着使用 JavaScript。如果你需要复习 JavaScript 语言的基础知识,请参见第四章。

在本章的这一部分,我将展示 DOM 的一些基本特性。在本书的其余部分,我将重点介绍如何使用 jQuery 访问 DOM,但是在这一节中,我将向您展示一些内置的支持,部分是为了强调 jQuery 方法可以更加优雅。

使用 DOM

为 DOM 中所有类型的元素定义基本功能的 JavaScript 对象称为HTMLElementHTMLElement对象定义了所有 HTML 元素类型共有的属性和方法,包括表 2-3 中显示的属性。

表 2-3 。基本 html 元素属性

财产 描述 返回
className 获取或设置元素所属的类的列表 string
id 获取或设置 id 属性的值 string
lang 获取或设置 lang 属性的值 string
tagName 返回标记名(指示元素类型) string

还有更多的属性可用。确切的设置取决于您正在使用的 HTML 版本。但是这四点足以让我演示 DOM 的基本工作方式。

DOM 使用从HTMLElement派生的对象来表示每个元素类型的独特特征。例如, HTMLImageElement对象用于表示 DOM 中的img元素,该对象定义了src属性,对应于img元素的src属性。我不打算详细讨论特定于元素的对象,但是作为一个规则,您可以依赖于与元素属性相对应的可用属性。

您通过全局document变量访问 DOM,该变量返回一个Document对象。Document对象代表浏览器正在显示的 HTML 文档,并定义了一些允许你在 DOM 中定位对象的方法,如表 2-4 所述。

表 2-4 。记录查找元素的方法

财产 描述 返回
getElementById(<id>) 返回具有指定的 id 值的元素 HTMLElement
getElementsByClassName(<class>) 返回具有指定的值的元素 HTMLElement[]
getElementsByTagName(<tag>) 返回指定类型的元素 HTMLElement[]
querySelector(<selector>) 返回与指定 CSS 选择器匹配的第一个元素 HTMLElement
querySelectorAll(<selector>) 返回与指定 CSS 选择器匹配的所有元素 HTMLElement[]

再说一次,我只是挑选对本书有用的方法。表格中描述的最后两种方法使用了 CSS 选择器,我在第三章中描述过。清单 2-15 展示了如何使用Document对象在文档中搜索特定类型的元素。

清单 2-15 。在 DOM 中搜索元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <style>
        h1 {
            width: 700px; border: thick double black; margin-left: auto;
            margin-right: auto; text-align: center; font-size: x-large; padding: .5em;
            color: darkgreen; background-image: url("border.png");
            background-size: contain; margin-top: 0;
        }
        .dtable {display: table;}
        .drow {display: table-row;}
        .dcell {display: table-cell; padding: 10px;}
        .dcell > * {vertical-align: middle}
        input {width: 2em; text-align: right; border: thin solid black; padding: 2px;}
        label {width: 5em;  padding-left: .5em;display: inline-block;}
        #buttonDiv {text-align: center;}
        #oblock {display: block; margin-left: auto; margin-right: auto; width: 700px;}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <div id="oblock">
            <div class="dtable">
                <div class="drow">
                    <div class="dcell">
                        <img src="aster.png"/><label for="aster">Aster:</label>
                        <input name="aster" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
    <script>
        var elements = document.getElementsByTagName("img");
        for (var i = 0; i < elements.length; i++) {
            console.log("Element: " + elements[i].tagName + " " + elements[i].src);
        }
    </script>
</body>
</html>

在清单 2-15 中,我在body元素的末尾添加了一个script元素。当浏览器在文档中找到一个script元素时,它们会在文档的其余部分被加载和处理之前,立即执行 JavaScript 语句。当您使用 DOM 时,这会带来一个问题,因为这意味着您通过Document对象对元素的搜索是在您感兴趣的对象在模型中被创建之前执行的。为了避免这种情况,我将script元素放在了文档的末尾。正如我在第二部分中解释的那样,jQuery 提供了一种处理这个问题的好方法。

在这个脚本中,我使用了getElementsByTagName方法来查找文档中所有的 img元素。该方法返回一个对象数组,我枚举该数组以将每个对象的tagNamesrc属性值打印到控制台。写入控制台的输出如下:

Element: IMG http://www.jacquisflowershop.com/jquery/aster.png
Element: IMG http://www.jacquisflowershop.com/jquery/daffodil.png
Element: IMG http://www.jacquisflowershop.com/jquery/rose.png
Element: IMG http://www.jacquisflowershop.com/jquery/peony.png
Element: IMG http://www.jacquisflowershop.com/jquery/primula.png
Element: IMG http://www.jacquisflowershop.com/jquery/snowdrop.png

修改 DOM

DOM 中的对象是活动的,这意味着改变 DOM 对象属性的值会影响浏览器显示的文档。清单 2-16 显示了一个具有这种效果的脚本。(我只是在这里显示了script元素,以减少重复。文档的其余部分与清单 2-15 中的相同。)

*清单 2-16 。修改 DOM 对象属性

...
<script>
    var elements = document.getElementsByTagName("img");
    for (var i = 0; i < elements.length; i++) {
        elements[i].src = "snowdrop.png";
    }
</script>
...

在这个脚本中,我将所有img元素的src属性值设置为snowdrop.png。你可以在图 2-6 中看到效果。

9781430263883_Fig02-06.jpg

图 2-6 。使用 DOM 修改 HTML 文档

修改样式

您可以使用 DOM 来更改 CSS 属性的值。(第三章如果你需要的话提供了 CSS 的入门。)DOM API 对 CSS 的支持是全面的,但最简单的技术是使用由HTMLElement对象定义的style属性。由style属性返回的对象定义了与 CSS 属性相对应的属性(我意识到这句话里有很多属性,对此我深表歉意)。

CSS 和style返回的对象定义的属性命名方案略有不同。例如,background-color CSS 属性变成了style.backgroundColor对象属性。清单 2-17 展示了使用 DOM 来管理样式。

清单 2-17 。使用 DOM 修改元素样式

...
<script>
    var elements = document.getElementsByTagName("img");
    for (var i = 0; i < elements.length; i++) {
        if (i > 0) {
            elements[i].style.opacity = 0.5;
        }
    }
</script>
...

在这个脚本中,我更改了文档中除第一个img元素之外的所有元素的opacity属性值。我保留了一个元素不变,所以你可以看到图 2-7 中的不同。

9781430263883_Fig02-07.jpg

图 2-7 。使用 JavaScript 更改 CSS 属性值

处理事件

事件是浏览器发送的信号,指示 DOM 中一个或多个元素的状态变化。有一系列事件来表示不同种类的状态变化。例如,当用户单击文档中的元素时会触发click事件,当用户提交表单时会触发submit元素。很多事件是有关联的。例如,当用户将鼠标移动到一个元素上时会触发mouseover事件,当用户再次将鼠标移出时会触发mouseout事件。您可以通过将 JavaScript 处理函数与 DOM 元素的事件相关联来响应事件。每次触发事件时,都会执行处理函数中的语句。清单 2-18 给出了一个例子。

清单 2-18 。处理事件

...
<script>
    var elements = document.getElementsByTagName("img");
    for (var i = 0; i < elements.length; i++) {
        elements[i].onmouseover = handleMouseOver;
        elements[i].onmouseout = handleMouseOut;
    }

    function handleMouseOver(e) {
        e.target.style.opacity = 0.5;
    }

    function handleMouseOut(e) {
        e.target.style.opacity = 1;
    }
</script>
...

这个脚本定义了两个处理函数,我将它们指定为img DOM 对象上的 onmouseoveronmouseout属性的值。这个脚本的效果是,当鼠标在图像上时,图像变得部分透明,当鼠标退出时,图像恢复正常。我不打算深入探讨 DOM API 事件处理机制,因为 jQuery 对事件的支持是第二部分的主题。然而,我确实想看看传递给事件处理函数的对象:Event对象。表 2-5 显示了Event对象中最重要的成员。

表 2-5 。事件对象的功能和属性

名字 描述 返回
type 事件的名称(即鼠标悬停 string
target 事件的目标元素 HTMLElement
currentTarget 当前正在调用其事件侦听器的元素 HTMLElement
eventPhase 事件生命周期中的阶段 number
bubbles 如果事件将在文档中冒泡,则返回true;否则返回 boolean
cancelable 如果事件有可以取消的默认动作,则返回true;否则返回 boolean
stopPropagation() 触发当前元素的事件侦听器后,停止事件在元素树中的流动 void
stopImmediatePropagation() 立即停止事件在元素树中的流动;当前元素的未触发事件侦听器将被忽略 void
preventDefault() 阻止浏览器执行与事件关联的默认操作 void
defaultPrevented 如果调用了 preventDefault() ,则返回 true boolean

在前面的例子中,我使用了target属性来获取触发事件的元素。其他一些成员与事件流默认动作相关,我将在下一节简要解释。这一章我只是在打基础。

了解事件流程

一个事件的生命周期有三个阶段:捕获目标冒泡。当事件被触发时,浏览器识别与事件相关的元素,该元素被称为事件的目标。浏览器识别body元素和目标之间的所有元素,并检查它们中的每一个,以查看它们是否有任何事件处理程序请求被通知它们后代的事件。浏览器在触发目标本身上的处理程序之前触发任何这样的处理程序。(我将在第二部分向您展示如何请求后代事件的通知。)

一旦捕获阶段完成,您就进入到目标阶段,,这是三个阶段中最简单的。当捕获阶段完成时,浏览器触发已经添加到目标元素的事件类型的任何侦听器。

一旦目标阶段完成,浏览器就开始沿着祖先元素链向上返回到 body 元素。在每个元素中,浏览器检查是否有事件类型的侦听器不支持捕获(我将在第二部分解释如何实现)。并非所有事件都支持冒泡。您可以使用bubbles属性查看事件是否会冒泡。值true表示事件会冒泡,false表示不会。

了解默认操作

一些事件定义了触发事件时将执行的默认操作。例如,a元素上的click事件的默认动作是浏览器将在href属性中指定的 URL 处加载内容。当一个事件有一个默认动作时,它的cancelable属性的值将是true。您可以通过调用preventDefault方法来停止默认动作的执行。注意,调用preventDefault函数不会停止事件在捕获、目标和冒泡阶段的流动。这些阶段仍将执行,但浏览器不会在冒泡阶段结束时执行默认操作。您可以通过读取defaultPrevented属性来测试查看preventDefault函数是否被早期的事件处理程序调用过。如果它返回true,那么preventDefault函数已经被调用。

摘要

在这一章中,我带你游览了 HTML,尽管没有详细描述 100 多个元素中的任何一个。我向您展示了如何创建和构造一个基本的 HTML 文档,元素如何包含文本内容和其他元素的混合,以及这如何导致具有特定关系类型的元素层次结构。我还向您展示了 DOM API 的基本用法以及它如何处理元素选择和事件——正如您将在本书中看到的,使用 jQuery 的一个主要原因是因为它隐藏了 DOM API 的细节,并使处理 HTML 元素和表示它们的 JavaScript 对象变得简单而容易。在第三章中,我提供了一个级联样式表的快速入门,它用于控制 HTML 元素的呈现。**

三、CSS 优先

级联样式表(CSS)是控制 HTML 元素外观(更恰当的说法是表示)的手段。CSS 对 jQuery 有着特殊的意义,原因有二。首先,你可以使用 CSS 选择器(我在本章中描述了它)告诉 jQuery 如何在 HTML 文档中查找元素。第二个原因是 jQuery 最常见的任务之一是改变应用于元素的 CSS 样式。

有超过 130 个 CSS 属性,每个属性控制一个元素表现的一个方面。与 HTML 元素一样,CSS 属性太多了,我无法在本书中描述它们。相反,我关注的是 CSS 如何工作以及如何将样式应用于元素。如果你想详细了解 CSS,那么我推荐我的另一本书:html 5权威指南,也是由 Apress 出版的。

CSS 入门

当浏览器在屏幕上显示一个元素时,它使用一组称为 CSS 属性 的属性来决定元素应该如何呈现。清单 3-1 显示了一个简单的 HTML 文档。

清单 3-1 。一个简单的 HTML 文档

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
</head>
<body>
    <h1>New Delivery Service</h1>
    <h2>Color and Beauty to Your Door</h2>
    <h2>(with special introductory offer)</h2>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

你可以在图 3-1 中看到浏览器是如何显示文档的。

9781430263883_Fig03-01.jpg

图 3-1 。在浏览器中显示简单文档

CSS 属性有很多——太多了,本书无法详细介绍——但是你可以通过查看表 3-1 中的少量属性来了解 CSS 是如何工作的。

*表 3-1 。一些 CSS 属性

财产 描述
color 设置元素的前景色(通常设置文本的颜色)
background-color 设置元素的背景色
font-size 设置元素中包含的文本所使用的字体大小
border 设置元素的边框

我没有为这些 CSS 属性定义值,但是浏览器仍然能够显示内容,如图 3-1 所示,每个内容元素都以不同的方式呈现。即使您没有为 CSS 属性提供值,浏览器仍然必须显示元素,因此每个元素都有一个样式约定——当 HTML 文档中没有设置其他值时,它使用 CSS 属性的默认值。HTML 规范定义了元素的样式约定,但是浏览器可以随意改变它们,这就是为什么你会看到,比如说,Google Chrome 和 Internet Explorer 之间样式约定的变化。表 3-2 显示了 Google Chrome 为表 3-1 中所列的属性 I 使用的默认值。

表 3-2 。一些 CSS 属性及其样式约定值

Tab03-02.jpg

从表中可以看出,所有三种类型的元素的colorbackground-colorborder属性的值都相同,只有font-size属性发生了变化。在这一章的后面,我将描述这些属性值所使用的单位——但是现在,我们将把重点放在设置属性值上,而不用担心这些值用什么单位来表示。

设置内联值

为 CSS 属性设置值的最直接的方法是将style属性应用于我们想要改变其表示的元素。清单 3-2 展示了这是如何做到的。

清单 3-2 。使用样式属性设置元素的 CSS 属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
</head>
<body>
    <h1>New Delivery Service</h1>
    <h2 style="background-color: grey; color: white">Color and Beauty to Your Door</h2>
    <h2>(with special introductory offer)</h2>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

在清单 3-2 中,我使用了样式声明来指定两个 CSS 属性的值。你可以在图 3-2 中看到属性值的剖析。

9781430263883_Fig03-02.jpg

图 3-2 。样式属性值的剖析

每个样式声明指定要更改的属性的名称和要使用的值,用冒号(:)分隔。您可以使用分号(;)将多个声明放在一起。在图 3-2 中,我将background-color的值设置为grey,将color属性的值设置为white。这些值在h2元素的style属性中指定,并且只影响该元素(文档中的其他元素不受影响,即使它们也是h2元素)。在图 3-3 的中,您可以看到这些新属性值对第一个h2元素的影响。

9781430263883_Fig03-03.jpg

图 3-3 。更改 h2 元素的 style 属性中 CSS 值的效果

定义嵌入样式

使用style属性很简单,但是它只适用于单个元素。您可以对想要更改的每个元素使用style属性,但是很快就会变得难以管理并且容易出错,尤其是如果您需要在以后进行修改的话。一个更强大的技术是使用style 元素(而不是style 属性)来定义一个嵌入样式,并指示浏览器使用选择器来应用它。清单 3-3 显示了一种嵌入式风格。

清单 3-3 。定义嵌入样式

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        h2 { background-color: grey; color: white;}
    </style>
</head>
<body>
    <h1>New Delivery Service</h1>
    <h2>Color and Beauty to Your Door</h2>
    <h2>(with special introductory offer)</h2>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

我们仍然在嵌入式风格中使用声明,但是它们被括在大括号中({}字符),并且在它们前面有一个选择器。你可以在图 3-4 中看到嵌入式风格的剖析。

9781430263883_Fig03-04.jpg

图 3-4 。嵌入式风格的剖析

image 提示我把style元素放在了head元素中,但是我也可以把它放在body元素中。我更喜欢对样式使用head元素,因为我喜欢将内容从控制外观的 CSS 中分离出来的想法。

CSS 选择器在 jQuery 中很重要,因为它们是选择元素并对其执行操作的基础。我在示例中使用的选择器是h2,这意味着大括号中包含的样式声明应该应用于文档中的每个h2元素。你可以在图 3-5 的中看到这对h2元素的影响。

9781430263883_Fig03-05.jpg

图 3-5 。嵌入样式的效果

您可以使用一个style元素来包含多个嵌入样式。清单 3-4 显示了您在第二章中第一次看到的花店文档,它有一组更复杂的样式。

清单 3-4 。HTML 文档中一组更复杂的样式

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <style>
        h1 {
            width: 700px; border: thick double black; margin-left: auto;
            margin-right: auto; text-align: center; font-size: x-large; padding: .5em;
            color: darkgreen; background-image: url("border.png");
            background-size: contain; margin-top: 0;
        }
        .dtable {display: table;}
        .drow {display: table-row;}
        .dcell {display: table-cell; padding: 10px;}
        .dcell > * {vertical-align: middle}
        input {width: 2em; text-align: right; border: thin solid black; padding: 2px;}
        label {width: 5em;  padding-left: .5em;display: inline-block;}
        #buttonDiv {text-align: center;}
        #oblock {display: block; margin-left: auto; margin-right: auto; width: 700px;}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <div id="oblock">
            <div class="dtable">
                <div class="drow">
                    <div class="dcell">
                        <img src="aster.png"/><label for="aster">Aster:</label>
                        <input name="aster" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

清单 3-4 中的style元素包含几个嵌入样式,其中一些,尤其是带有h1选择器的元素,定义了许多属性的值。

定义外部样式表

您可以创建一个单独的样式表,而不是在每个 HTML 文档中定义相同的样式集。这是一个独立的文件,带有传统的.css文件扩展名,你可以将你的风格放入其中。清单 3-5 显示了文件styles.css的内容,我已经将花店文档中的样式放入其中。

清单 3-5 。styles.css 文件

h1 {
    min-width: 700px; border: thick double black; margin-left: auto;
    margin-right: auto; text-align: center; font-size: x-large; padding: .5em;
    color: darkgreen; background-image: url("border.png");
    background-size: contain; margin-top: 0;
}
.dtable {display: table;}
.drow {display: table-row;}
.dcell {display: table-cell; padding: 10px;}
.dcell > * {vertical-align: middle}
input {width: 2em; text-align: right; border: thin solid black; padding: 2px;}
label {width: 5em;  padding-left: .5em;display: inline-block;}
#buttonDiv {text-align: center;}
#oblock {display: block; margin-left: auto; margin-right: auto; min-width: 700px;}

你不需要在样式表中使用style元素。您只需直接定义选择器和声明。然后使用link元素将样式带入文档,如清单 3-6 所示。

清单 3-6 。导入外部样式表

<!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" />
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <div id="oblock">
            <div class="dtable">
                <div class="drow">
                    <div class="dcell">
                        <img src="aster.png"/><label for="aster">Aster:</label>
                        <input name="aster" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

您可以根据需要链接到任意多个样式表,每个link元素引用一个样式表。如果用同一个选择器定义两个样式,导入样式表的顺序很重要。最后加载的将是应用的那个。

了解 CSS 选择器

注意在花店样式表中有不同种类的选择器:一些是元素名称(比如h1input),一些以句点开头(比如.dtable.row),还有一些以英镑开头(#butonDiv#oblock)。如果你特别细心,你会注意到其中一个选择器有多个组件:.dcell > *。每个 CSS 选择器选择文档中的元素,不同种类的选择器告诉浏览器以不同的方式寻找元素。在本节中,我描述了 CSS 定义的不同种类的选择器,从核心选择器 开始,表 3-3 总结了这些选择器。

表 3-3 。核心选择器

选择器 描述
* 选择所有元素
<type> 选择指定类型的元素
.<class> 选择特定类的元素(不考虑元素类型)
<type>.<class> 选择属于指定类成员的指定类型的元素
#<id> id 属性选择具有指定值的元素

这些选择器是使用最广泛的(例如,它们涵盖了我在示例文档中定义的大多数样式)。

按属性选择

虽然基本的选择器作用于idclass属性(我在第二章中描述过),但是也有选择器可以让你处理任何属性。表 3-4 对它们进行了描述。

表 3-4 。属性选择器

选择器 描述
[attr] 选择定义属性 attr 的元素,而不考虑分配给该属性的值
[attr="val"] 选择定义属性并且该属性的值为的元素
[attr^="val"] 选择定义属性的元素,其属性值以字符串 val 开始
[attr$="val"] 选择定义属性并且其属性值以字符串 val 结尾的元素
[attr*="val"] 选择定义属性并且其属性值包含字符串 val 的元素
[attr∼="val"] 选择定义属性并且其属性值包含多个值的元素,其中一个值为
[attr&#124;="val"] 选择定义属性的元素,其值是用连字符分隔的值列表,第一个是

清单 3-7 显示了一个简单的嵌入样式的文档,它的选择器是基于属性的。

清单 3-7 。使用属性选择器

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        [lang] { background-color: grey; color: white;}
        [lang="es"] {font-size: 14px;}
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <h2 lang="es">(Color y belleza a tu puerta)</h2>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

第一个选择器匹配任何具有lang属性的元素,第二个选择器匹配任何lang属性值为es的元素。你可以在图 3-6 中看到这些风格的效果。

9781430263883_Fig03-06.jpg

图 3-6 。使用属性选择器应用样式

image 注意这个图有一些重要的地方需要注意。看看h2元素是如何受到嵌入样式的的影响的。第一种样式应用于所有具有lang属性的元素。第二种样式应用于所有具有值为eslang属性的元素。文档中的第二个h2元素满足这两个条件,因此background-colorcolorfont-size属性的值都被更改。我将在“理解样式级联”一节中详细解释这是如何工作的

按关系选择

在第二章的中,我解释了元素(以及在 DOM 中表示它们的对象)有一个层次结构,它产生了不同种类的关系。有 CSS 选择器允许你根据那些关系选择元素,如表 3-5 所述。

表 3-5 。关系选择器

选择器 描述
<selector> <selector> 选择与第二个选择器匹配并且是第一个选择器匹配的元素的后代的元素
<selector> > <selector> 选择与第二个选择器匹配的元素,以及与第一个选择器匹配的元素的子元素
<selector> + <selector> 选择与第二个选择器匹配并且是与第一个选择器匹配的元素的下一个同级元素
<selector> ∼ <selector> 选择与第二个选择器匹配的元素,并且这些元素是与第一个选择器匹配的元素的同级元素(并且出现在第一个选择器之后)

我在花店示例文档中使用了其中一个选择器,如下所示:

.dcell > * {vertical-align: middle}

这个选择器匹配所有属于dcell类的元素的子元素,声明将vertical-align属性设置为值middle。清单 3-8 展示了其他一些正在使用的关系选择器。

清单 3-8 。使用关系选择器

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        h1 ∼ [lang] { background-color: grey; color: white;}
        h1 + [lang] {font-size: 12px;}
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <h2 lang="es">(Color y belleza a tu puerta)</h2>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

我已经使用了清单 3-8 中的两个兄弟选择器。第一个选择器使用波浪号()字符,匹配任何具有lang属性的元素,该属性在h1元素之后定义,并且是其同级元素。在示例文档中,这意味着两个h2元素都被选中(因为它们具有属性,是h1元素的兄弟元素,并且是在h2元素之后定义的)。第二个选择器,使用加号字符的那个,是相似的,但是只匹配一个h1元素的直接兄弟。这意味着只选择了第一个h2元素。在图 3-7 中可以看到效果。

9781430263883_Fig03-07.jpg

图 3-7 。使用兄弟关系选择器

使用伪元素和伪类选择器进行选择

CSS 支持一组伪元素和伪类选择器。这些提供了方便的功能,并不直接对应于文档中的元素或类成员。表 3-6 描述了这些选择器。

表 3-6 。伪选择器

选择器 描述
:active 选择用户当前激活的元素;这通常是指当鼠标按钮被按下时指针下的那些元素
:checked 选择处于选中状态的元素
:default 选择默认元素
:disabled 选择处于禁用状态的元素
:empty 选择不包含子元素的元素
:enabled 选择处于启用状态的元素
:first-child 选择作为其父元素的第一个子元素的元素
:first-letter 选择文本块的第一个字母
:first-line 选择文本块的第一行
:focus 选择具有焦点的元素
:hover 选择占据屏幕上鼠标指针下方位置的元素
:in-range :out-of-range 选择指定范围内或范围外的受约束的输入元素
:lang(<language>) 基于 lang 属性的值选择元素
:last-child 选择作为其父元素的最后一个子元素的元素
:link 选择链接元素
:nth-child(n) 选择其父元素的第 n 个子元素
:nth-last-child(n) 选择从其父元素的最后一个子元素开始第 n 个元素
:nth-last-of-type(n) 选择从其父元素定义的类型的最后一个子元素开始的第 n 个元素
:nth-of-type(n) 选择属于其父元素定义的类型的第 n 个子元素
:only-child 选择由其父元素定义的唯一元素
:only-of-type 选择由其父元素定义的类型的唯一元素
:required :optional 基于必需的属性的存在,选择输入元素
:root 选择文档中的根元素
:target 选择由 URL 片段标识符引用的元素
:valid :invalid 根据表单中的输入验证选择有效或无效的输入元素
:visited 选择用户已经访问过的链接元素

清单 3-9 展示了一些伪选择器的使用。

清单 3-9 。使用伪选择器

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        :nth-of-type(2) { background-color: grey; color: white;}
        p:first-letter {font-size: 40px;}
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <h2 lang="es">(Color y belleza a tu puerta)</h2>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

您可以单独使用伪选择器,或者作为另一个选择器的修饰符。我已经在清单 3-9 中展示了这两种方法。第一个选择器匹配任何属于其父元素定义的类型的第二个元素。第二个选择器匹配任何p元素的第一个字母。你可以在图 3-8 中看到这些风格的应用。

9781430263883_Fig03-08.jpg

图 3-8 。使用伪选择器来应用样式

联合和否定选择器

通过将选择器排列在一起,可以获得额外的灵活性。具体来说,您可以通过合并选择和通过否定反转选择来创建联合。这两种方法在表 3-7 中均有描述。

表 3-7 。灵活安排选择器

选择器 描述
<selector>, <selector> 选择第一个选择器匹配的元素和第二个选择器匹配的元素的并集
:not(<selector>) 选择与指定选择器不匹配的元素

清单 3-10 展示了如何创建并集和反集。

清单 3-10 。使用选择器联合和求反

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        h1, h2 { background-color: grey; color: white;}
        :not(html):not(body):not(:first-child) {border: medium double black;}
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

清单 3-10 中的第一个选择器是h1h2选择器的联合。正如您所想象的,这匹配文档中的所有h1h2元素。第二个选择器有点深奥。我想演示如何使用伪选择器作为其他伪选择器的修饰符,包括否定。

...
:not(html):not(body):not(:first-child) {border: medium double black;}
...

该选择器匹配任何不是html元素、不是body元素并且不是其父元素的第一个子元素的元素。你可以在图 3-9 中看到这个例子中的样式是如何应用的。

9781430263883_Fig03-09.jpg

图 3-9 。创建选择器联合和否定

了解样式层叠

理解样式表的关键是理解它们如何级联继承。HTML 文档中的 CSS 属性可能有多个来源,而层叠和继承是浏览器确定应该使用哪些值来显示元素的方法。您已经看到了定义样式的三种不同方式(内嵌、嵌入和来自外部样式表),但是还有另外两种样式来源:浏览器样式用户样式

如果没有指定其他样式,浏览器样式 (更确切地说是用户代理样式)是浏览器应用于元素的样式约定。你在本章开始时看到了一个使用样式约定的例子。

此外,大多数浏览器允许用户定义自己的样式表。这些样式表包含的样式被称为用户样式 。这不是一个广泛使用的特性,但是那些定义自己的样式表的用户通常非常重视能够这样做,尤其是因为它提供了一种使页面更易访问的方法。

每个浏览器都有自己的用户风格机制。例如,Windows 上的谷歌 Chrome 在用户的个人资料目录中创建了一个名为User StyleSheets\Custom.css的文件。添加到这个文件中的任何样式都将应用到用户访问的任何站点上,服从我在下一节中描述的级联规则。

了解样式如何级联

现在您已经看到了浏览器必须考虑的所有样式来源,您可以看看浏览器在显示元素时查找属性值的顺序。

  1. 内联样式(使用元素上的style属性定义的样式)
  2. 嵌入样式(在style元素中定义的样式)
  3. 外部样式(使用link元素导入的样式)
  4. 用户样式(由用户定义的样式)
  5. 浏览器样式(浏览器应用的样式约定)

假设浏览器需要显示一个p元素。它需要知道应该使用什么颜色来显示文本,这意味着它需要为 CSS color属性找到一个值。首先,它将检查试图显示的元素是否有定义了color值的内联样式,如下所示:

...
<pstyle="color: red">We are pleased to announce that we are starting a home delivery
    service for your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
...

如果没有内联样式,那么浏览器将查找包含应用于该元素的样式的style元素,如下所示:

...
<style>
    p {color: red};
</style>
...

如果没有这样的style元素,浏览器会查看通过link元素加载的样式表,依此类推,直到浏览器为color属性找到一个值,这意味着如果没有其他值可用,就使用默认浏览器样式中定义的值。

image 提示前三种属性来源(内嵌样式、嵌入样式和样式表)统称为作者样式。用户样式表中定义的样式称为用户样式,浏览器定义的样式称为浏览器样式

调整重要样式的顺序

您可以通过将属性值标记为重要 来覆盖正常的层叠顺序,如清单 3-11 中的所示。

清单 3-11 。将样式属性标记为重要

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
<style>
    p {color: black !important; }
</style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <pstyle="color: red">We are pleased to announce that we are starting a home delivery
    service for your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

通过将!important追加到声明中,可以将单个值标记为重要。浏览器优先选择重要的样式,而不管它们是在哪里定义的。您可以在图 3-10 中看到属性重要性的影响,其中color属性的嵌入值覆盖了内嵌值(这在打印页面上可能有点难以辨认,但是所有的文本都是黑色的)。

9781430263883_Fig03-10.jpg

图 3-10 。重要属性值覆盖内联属性值

image 提示唯一优先于您定义的重要值的是在用户样式表中定义的重要值。对于常规值,作者样式在用户样式之前使用,但是在处理重要值时,情况正好相反。

特异性和顺序评估平分秋色

如果有两种样式可以应用于在同一级联级别定义的元素,并且它们都包含浏览器正在寻找的 CSS 属性值,那么我们就进入了平局决胜的情况。为了决定使用哪个值,浏览器评估每种风格的特异性,并选择最具体的一个。浏览器通过计算三种不同的特征来确定一种风格的特殊性。

  • 样式选择器中的id值的数量
  • 选择器中其他属性和伪类的数量
  • 选择器中元素名称和伪元素的数量

浏览器会组合每个评估的值,并应用最具体的样式的属性值。你可以在清单 3-12 中看到一个简单的特异性例子。

清单 3-12 。风格的特殊性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        p {background-color: grey; color: white;}
        p.details {color:red;}
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <p class="details">We are pleased to announce that we are starting a home delivery
    service for your flower needs. We will deliver within a 20 mile radius of the store
    for free and $1/mile thereafter.</p>
</body>
</html>

在评估特异性时,您以a - b - c的形式创建一个数字,其中每个字母都是所统计的三个特征之一的总和。这不是三位数。如果样式的a值最大,则样式更具体。只有当a值相等时,浏览器才会比较b值。在这种情况下,b值越大的样式越特殊。只有当ab值相同时,浏览器才会考虑c值。这意味着1 - 0 - 0的特异性分数比0 - 5 - 5的特异性分数更高。

在这种情况下,选择器p.details包含一个class属性,这意味着样式的特异性是0 - 1 - 1 ( 0 id 值+ 1其他属性+ 1元素名称)。另一个选择器的特异性为0 - 0 - 1(不包含id值或其他属性,只有一个元素名)。

当呈现一个p元素时,浏览器将为color属性寻找一个值。如果p元素是details类的成员,那么带有p.details选择器的样式将是最具体的,并且将使用red的值。对于所有其他p元素,将使用值white。在图 3-11 中,您可以看到浏览器如何选择和应用该示例的值。

9781430263883_Fig03-11.jpg

图 3-11 。基于特定性应用样式中的值

当样式中定义了具有相同特性的值时,浏览器会根据定义值的顺序选择它所使用的值。最后定义的是将要使用的。清单 3-13 显示了一个包含两种相同特定样式的文档。

清单 3-13 。同样独特的风格

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        p.details {color:red;}
        p.information {color: blue;}
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <p class="details information">We are pleased to announce that we are starting a home
    Delivery service for your flower needs. We will deliver within a 20 mile radius of
    the store for free and $1/mile thereafter.</p>
</body>
</html>

style元素中定义的两种样式具有相同的特异性分数,并且都适用于p元素。当浏览器在页面中显示p元素时,它将为color属性选择blue属性,因为这是后一种样式中定义的值。你可以在图 3-12 中看到这一点。

9781430263883_Fig03-12.jpg

图 3-12 。根据定义样式的顺序选择属性值

image 提示这些特异性规则仅适用于在同一层级定义的样式。例如,这意味着在style属性中定义的属性总是优先于在style元素中定义的样式。

了解 CSS 单位

在本章的前面,我向你展示了浏览器默认使用的 CSS 属性的值。这些是例子中一些元素的样式约定,我在表 3-8 中复制了这些信息。

表 3-8 。一些 CSS 属性及其值

Tab03-08.jpg

CSS 定义了一系列不同的单元类型,在接下来的章节中,我将展示一些更常用的单元类型,包括我在本书中使用的单元类型。

使用 CSS 颜色

许多 CSS 属性都使用颜色,包括我在本章中使用的colorbackground-color属性。指定颜色的最简单方法是使用预定义的颜色名称,或者为每个红色、绿色和蓝色分量使用十进制或十六进制值。十进制数值用逗号分隔,十六进制数值通常以#为前缀,如#ffffff,代表白色。你可以在表 3-9 中看到一些预定义的颜色名称及其十进制和十六进制的对等物。

表 3-9 。选定的 CSS 颜色

Tab03-09.jpg

这些被称为基本颜色名称。CSS 还定义了扩展颜色。这里不胜枚举,但你可以在www.w3.org/TR/css3-color找到完整的列表。除了基本颜色之外,还有许多细微的变化。作为一个例子,表 3-10 显示了可以使用的灰色阴影扩展集。

表 3-10 。选定的 CSS 颜色

颜色名称 十六进制 小数
darkgrey #a9a9a9 169,169,169
darkslategrey #2f4f4f 47,79,79
dimgrey #696969 105,105,105
grey #808080 128,128,128
lightgrey #d3d3d3 211,211,211
lightslategrey #778899 119,136,153
slategrey #708090 112,128,144

指定更复杂的颜色

颜色名称和简单的十六进制值不是指定颜色的唯一方式。许多功能允许您选择颜色。表 3-11 描述了每个可用的功能。

表 3-11 。CSS 颜色函数

功能 描述 例子
rgb(r, g, b) 使用 RGB(红、绿、蓝)模型指定颜色。 color: rgb(112, 128, 144)
rgba(r, g, b, a) 使用 RGB 模型指定颜色,并添加 alpha 值来指定不透明度。(值为 0 表示完全透明;值为 1 表示完全不透明。) color: rgba(112, 128, 144, 0.4)
hsl(h, s, l) 使用色调、饱和度和亮度(HSL)模型指定颜色。 color: hsl(120, 100%, 22%)
hsla(h, s, l, a) 类似于 HSL,但是增加了一个 alpha 值来指定不透明度。 color: hsla(120, 100%, 22%, 0.4)

你可以使用rgba函数来指定一个透明的颜色,但是如果你想要一个完全透明的元素,那么你可以使用特殊的颜色值transparent

了解 CSS 长度

许多 CSS 属性要求您指定一个长度,例如font-size属性,它用于指定用于呈现元素内容的字体大小。指定长度时,将单位数和单位标识符连接在一起,中间没有任何空格或其他字符。例如,font-size属性的值20pt表示由pt标识符表示的 20 个单位(它们是,稍后解释)。CSS 定义了两种长度单位:绝对长度单位和相对于另一个属性的长度单位。我将在接下来的章节中解释这两个问题。

使用绝对长度

绝对单位是真实世界的测量值。CSS 支持五种类型的绝对单位,表 3-12 对此进行了描述。

表 3-12 。绝对测量单位

单位标识符 描述
中的 英寸
厘米 厘米
毫米 毫米
pt 磅(1 磅等于 1/72 英寸)
pc 十二点活字(1 点活字等于 12 点)

您可以在样式中混合和匹配单位,以及混合绝对和相对单位。如果您事先知道内容将如何呈现,例如在设计印刷时,绝对单位会很有用。我在我的 CSS 项目中很少使用绝对单位——我发现相对单位更灵活,更容易维护,我很少创建必须符合现实世界测量的内容。

提示你可能想知道像素在绝对单位表中的位置。事实上,CSS 试图让像素成为一个相对的度量单位,尽管可悲的是,规范在这方面做了拙劣的尝试。您可以在“使用像素”一节中了解更多信息

使用相对长度

相对长度比绝对长度更复杂,需要简洁明了的语言来明确定义它们的含义。相对单位是用其他单位来衡量的。不幸的是,CSS 规范中的语言不够精确(这个问题已经困扰 CSS 多年)。这意味着 CSS 定义了广泛的有趣和有用的相对度量,但你不能使用其中的一些,因为它们没有广泛或一致的浏览器支持。表 3-13 显示了 CSS 定义的、在主流浏览器中可以依赖的相对单位。

表 3-13 。CSS 相对测量单位

单位标识符 描述
全身长的 相对于元素的字体大小
不包括 相对于元素字体的 x 高度
雷姆 相对于根元素的字体大小
像素 CSS 像素的数量(假设在 96 dpi 的显示器上)
% 另一财产价值的百分比

当您使用相对单位时,您实际上是指定了另一个度量的倍数。清单 3-14 给出了一个设置与font-size相关的属性的例子。

清单 3-14 。使用相对单位

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        p.details {
            font-size: 15pt;
            height: 3em;
            border: thin solid black;
        }
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <p class="details information">We are pleased to announce that we are starting a home
    delivery service for your flower needs. We will deliver within a 20 mile radius of
    the store for free and $1/mile thereafter.</p>
</body>
</html>

在这个例子中,我已经指定了height属性的值(它设置了一个元素的高度)为3em,这意味着p元素应该被渲染,使得元素在屏幕上的高度是font-size的三倍。你可以在图 3-13 中看到浏览器如何显示这些元素。我添加了一个边框(使用border属性),这样你可以更容易地看到元素的大小。

9781430263883_Fig03-13.jpg

图 3-13 。使用相对测量的效果

使用像素

CSS 中的像素可能不是你所期望的。术语像素的通常含义是指显示器上最小的可寻址单元:一个像素。CSS 尝试做一些不同的事情,定义一个像素如下:

参考像素是设备上一个像素的视角,像素密度为 96 dpi,与阅读器的距离为一臂长。

这就是困扰 CSS 的那种模糊定义。我不想咆哮,但是依赖于用户手臂长度的规范是有问题的。幸运的是,主流浏览器忽略了 CSS 定义的像素和显示中的像素之间的差异,并将 1 像素视为 1/96 英寸。(这是标准的 Windows 像素密度;在具有不同像素密度的显示器的平台上的浏览器通常实现一种转换,使得 1 像素仍然是大约 1/96 英寸。)

image 提示虽然没什么用,但是你可以在www.w3.org/TR/CSS21/syndata.html#length-units阅读 CSS 像素的完整定义。

这样做的净效果是,尽管 CSS 像素是一个相对的度量单位,但它们被浏览器视为一个绝对的单位。清单 3-15 演示了在 CSS 样式中指定像素。

清单 3-15 。在样式中使用像素单位

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        p.details {
            font-size: 20px;
            width: 400px;
            border: thin solid black;
        }
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <p class="details information">We are pleased to announce that we are starting a home
    delivery service for your flower needs. We will deliver within a 20 mile radius of
    the store for free and $1/mile thereafter.</p>
</body>
</html>

在清单 3-15 中,我用像素表示了font-sizewidth属性(width属性是对height属性的补充,它设置了一个元素的宽度)。你可以在图 3-14 中看到浏览器如何应用这种风格。

9781430263883_Fig03-14.jpg

图 3-14 。以像素为单位指定单位

image 提示虽然我经常在 CSS 中使用像素作为单位,但这往往是习惯问题。我发现em单位更灵活。这是因为只有当我需要改变时,我才需要改变字体的大小,而样式的其他部分可以无缝地工作。重要的是要记住,虽然 CSS 像素是相对单位,但实际上它们是绝对单位,因此可能是不灵活的。

使用百分比

您可以将度量单位表示为另一个属性值的百分比。你可以使用%(百分比)单位 来完成,如清单 3-16 中的所示。

清单 3-16 。将单位表示为另一个属性值的百分比

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style>
        p.details {
            font-size: 200%;
            width: 50%;
            border: thin solid black;
        }
    </style>
</head>
<body>
    <h1 lang="en">New Delivery Service</h1>
    <h2 lang="en">Color and Beauty to Your Door</h2>
    <p class="details information">We are pleased to announce that we are starting a home
    delivery service for your flower needs. We will deliver within a 20 mile radius of
    the store for free and $1/mile thereafter.</p>
</body>
</html>

使用百分比作为单位有两个复杂之处。首先,不是所有的属性都可以用这种方式表示,其次,可以用百分比表示的每个属性分别定义了百分比所指的其他属性。例如,font-size属性使用从父元素继承的font-size值,width属性使用包含元素的width

使用速记属性和自定义值

并非所有属性都使用单位和颜色设置。有些人有特殊的价值观,这是他们所控制的那种行为所独有的。一个很好的例子是border属性,我在一些清单中使用它来绘制元素的边框。您使用三个值来设置border属性,如下所示:

...
border: thin solid black;
...

第一个值是边框的粗细,第二个值是边框的样式,最后一个值是边框的颜色。表 3-14 显示了可用于指定边框粗细的值。

表 3-14 。边框宽度值

价值 描述
<长度> 以 CSS 度量单位表示的长度,如 empxcm
< perc > % 将围绕其绘制边框的区域的宽度的百分比
thin``medium 预设宽度,其含义由每个浏览器定义,但逐渐变粗

表 3-15 显示了可用于边框样式的值。

表 3-15 。边框样式的值

价值 描述
none 不会绘制任何边框
dashed 边框将是一系列矩形虚线
dotted 边界将是一系列圆点
double 边界将是两条平行线,两条平行线之间有一个间隙
groove 边框看起来会凹陷到页面中
inset 边框将使内容看起来凹陷到页面中
outset 边界将是这样的,内容看起来从页面上升
ridge 边框将从页面上凸起
solid 边界将是一条单一的、完整的线

通过将这些表中的值与一种颜色相结合,可以获得各种各样的边框效果。您可以在图 3-15 中看到浏览器中显示的样式范围。

9781430263883_Fig03-15.jpg

图 3-15 。边框样式

border属性也是一个很好的简写属性 的例子。这些属性允许您在一个声明中设置几个相关属性的值。这意味着前面所示的border等价于下面的 12 个声明:

border-top-color: black;
border-top-style: solid;
border-top-width: thin;
border-bottom-color: black;
border-bottom-style: solid;
border-bottom-width: thin;
border-left-color: black;
border-left-style: solid;
border-left-width: thin;
border-right-color: black;
border-right-style: solid;
border-right-width: thin;

CSS 允许您深入细节并设置单个属性进行精细控制,或者在所有相关值都相同时使用简写属性。

摘要

在这一章中,我给了你一个 CSS 的简要概述,向你展示了如何使用style属性设置属性,如何使用style元素(包括各种可用的选择器),以及浏览器如何使用层叠和特异性来确定当元素被显示时哪些属性值应该被应用于元素。最后,我浏览了 CSS 单元、自定义值和速记属性。可以用多种不同的方式表达属性值,这为 CSS 样式增加了灵活性(也增加了一点混乱)。

在第四章中,我介绍了 JavaScript 的基础知识,这是定义 jQuery 功能并将其应用于 HTML 内容的方法。*

四、JavaScript 优先

jQuery 是一个 JavaScript 库,可以添加到 HTML 文档中,由浏览器执行。您还可以通过向文档添加自己的代码来利用 jQuery 库的特性——这需要对如何编写 JavaScript 有一个基本的了解。在这一章中,我提供了 JavaScript 语言的初级读本,重点放在与 jQuery 相关的特性上。

JavaScript 作为一种编程语言有着好坏参半的名声。诚然,JavaScript 经历了一段艰难的生活,在它有机会完全成熟之前就匆匆通过了标准化,这给 JavaScript 的工作方式留下了一些奇怪之处。但是大多数对 JavaScript 的抱怨来自开发人员,他们发现 JavaScript 的工作方式与他们首选的后端语言(如 C#、Java 或 Python)并不完全相同。

一旦克服了接受 JavaScript 是一种独立语言的障碍,你将会发现一种灵活的、动态的、令人愉快的语言。当然,仍然有一些奇怪的地方,但是总的体验是积极的,只要稍加努力,你就会发现 JavaScript 是一种富有表现力和有价值的语言。

如果你是编程新手,一个好的起点是发布在流行网站【Lifehacker.com】的一系列文章。不需要任何编程知识,所有的例子都是用 JavaScript 编写的。该指南可从这里获得:http://lifehacker.com/5744113/learn-to-code-the-full-beginners-guide

准备使用 JavaScript

JavaScript 代码作为脚本添加到 HTML 文档中——浏览器将执行的 JavaScript 语句块——并且有不同的方式可以添加脚本。您可以定义一个内联脚本 ,其中脚本的内容是 HTML 文档的一部分。您还可以定义一个外部脚本 ,其中 JavaScript 包含在一个单独的文件中,并通过 URL 引用(这就是您访问 jQuery 库的方式,您将在第二部分中看到)。这两种方法都依赖于script元素。在这一章中,我将使用内联脚本。你可以在清单 4-1 中看到一个简单的例子。

清单 4-1 。一个简单的内联脚本

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        console.log("Hello");
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该脚本向控制台写入一条消息。控制台是浏览器提供的一个基本的(但是有用的)工具,可以让你在脚本执行时显示调试信息。每个浏览器都有不同的控制台显示方式。对于谷歌浏览器,你可以从Tools菜单中选择JavaScript console。你可以在图 4-1 中看到控制台是如何在 Chrome 中显示的;其他浏览器也有类似的功能。

9781430263883_Fig04-01.jpg

图 4-1 。谷歌浏览器 JavaScript 控制台

您可以看到控制台窗口中显示了调用console.log方法的输出,以及消息来源的详细信息(在本例中是在example.html文件的第 6 行)。这一章,我就不截图了;我将展示一些例子的结果。例如,对于清单 4-1 中的,输出如下:

Hello

我在本章的后面格式化了一些结果,使它们更容易阅读。在接下来的小节中,我将向您展示 JavaScript 语言的核心特性。如果你有过用其他现代语言编程的经验,你会发现 JavaScript 的语法和风格很熟悉——尽管正如我在本章开始时所说的,有些奇怪。

使用语句

基本的 JavaScript 构建块是 语句。每条语句代表一条命令,语句通常以分号(;)结束。分号是可选的,但是使用分号会使代码更容易阅读,并且允许在一行中有多个语句。清单 4-2 显示了一个脚本中的一对语句,它们是使用一个script元素定义的。

清单 4-2 。使用 JavaScript 语句

<!DOCTYPE HTML>
<html>
    <head>
        <title>Example</title>
        <script type="text/javascript">
            console.log("This is a statement");
            console.log("This is also a statement");
        </script>
    </head>
    <body>
        This is a simple example
    </body>
</html>

浏览器依次执行每条语句。在这个例子中,我简单地向控制台写出了一对消息。结果如下:

This is a statement
This is also a statement

定义和使用函数

当浏览器到达一个script元素时,它会立即一个接一个地执行 JavaScript 语句。你也可以将多条语句打包成一个函数,直到浏览器遇到一条调用该函数的语句才会执行,如清单 4-3 所示。

清单 4-3 。定义 JavaScript 函数

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        function myFunc() {
            console.log("This is a statement");
        };

        myFunc();
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

一个函数包含的语句用大括号({})括起来,称为代码块。这个清单定义了一个名为myFunc的函数,它在代码块中包含一条语句。JavaScript 是一种区分大小写的语言,这意味着关键字function必须是小写的。在浏览器到达另一个调用myFunc函数 的语句之前,函数中的语句不会被执行,如下:

myFunc();

在函数中执行语句会产生以下输出:

This is a statement

除了演示函数是如何定义的,这个例子没有什么特别的用处,因为函数在定义后会立即被调用。在第二部分中,您将看到一些更有用的函数示例。

用参数定义函数

与大多数编程语言一样,JavaScript 允许你为函数定义参数,如清单 4-4 所示。

清单 4-4 。用参数定义函数

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        function myFunc(name, weather) {
            console.log("Hello " + name + ".");
            console.log("It is " + weather + " today");
        };

        myFunc("Adam", "sunny");
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

我给myFunc函数添加了两个参数,称为nameweather。JavaScript 是一种动态类型语言,这意味着在定义函数时不必声明参数的数据类型。当我在本章后面讲述 JavaScript 变量时,我会回到动态类型。要调用带参数的函数,需要在调用函数时提供值作为参数,如下所示:

...
myFunc("Adam", "sunny");
...

该清单的结果如下:

Hello Adam.
It is sunny today

当你调用一个函数时,参数的数量不需要与函数中的参数数量相匹配。如果您调用的函数的实参数少于它的形参数,那么您没有为其提供值的任何形参的值都是未定义的。如果调用函数时使用的参数多于实际参数,那么多余的参数将被忽略。

这样做的结果是,您不能创建两个具有相同名称和不同参数的函数,并期望 JavaScript 根据您在调用函数时提供的参数来区分它们。这被称为多态性 ,尽管它在 Java 和 C#等语言中受支持,但在 JavaScript 中却不可用。相反,如果您定义了两个同名的函数,那么第二个定义将替换第一个定义。

定义返回结果的函数

您可以使用return 关键字从函数中返回结果。清单 4-5 显示了一个返回结果的函数。

清单 4-5 。从函数返回结果

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        function myFunc(name) {
            return ("Hello " + name + ".");
        };

        console.log(myFunc("Adam"));
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

这个函数定义了一个参数,并用它来产生一个结果。我调用函数并将结果作为参数传递给console.log函数,如下所示:

...
console.log(myFunc("Adam"));
...

请注意,您不必声明该函数将返回一个结果或表示结果的数据类型。该清单的结果如下:

Hello Adam.

使用变量和类型

您可以使用var关键字定义变量,并且可以选择在一条语句中为变量赋值。函数中定义的变量是局部变量,并且只能在该函数中使用。在script元素中直接定义的变量是全局变量,可以在任何地方访问,包括同一个 HTML 文档中的其他脚本。清单 4-6 展示了局部和全局变量的使用。

清单 4-6 。使用局部和全局变量

<!DOCTYPE HTML>
<html>
    <head>
        <title>Example</title>
        <script type="text/javascript">
            var myGlobalVar = "apples";

            function myFunc(name) {
                var myLocalVar = "sunny";
                return ("Hello " + name + ". Today is " + myLocalVar + ".");
            };
            console.log(myFunc("Adam"));
        </script>
        <script type="text/javascript">
            console.log("I like " + myGlobalVar);
        </script>
    </head>
    <body>
        This is a simple example
    </body>
</html>

同样,JavaScript 是一种动态类型语言。这并不意味着 JavaScript 没有类型:这只是意味着您不必显式声明变量的类型,并且您可以毫无困难地将不同的类型赋给同一个变量。JavaScript 将根据您分配给变量的值来确定类型,并根据使用它们的上下文在类型之间自由转换。来自清单 4-6 的结果如下:

Hello Adam. Today is sunny.
I like apples

使用原始类型

JavaScript 定义了一组原语类型:stringnumberboolean。这似乎是一个很短的列表,但是 JavaScript 设法将很大的灵活性融入到这三种类型中。

使用字符串

使用双引号或单引号字符定义string值,如清单 4-7 所示。

清单 4-7 。定义字符串变量

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var firstString = "This is a string";
        var secondString = 'And so is this';
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

您使用的引号字符必须匹配。例如,你不能用单引号开始一个字符串,然后用双引号结束。此列表没有控制台输出。

使用布尔值

boolean类型有两个值:truefalse。清单 4-8 显示了正在使用的两个值,但是这种类型在条件语句中使用时最有用,比如一个if语句。该清单中没有控制台输出。

清单 4-8 。定义布尔值

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var firstBool = true;
        var secondBool = false;
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

使用数字

number类型用于表示整数浮点数(也称为实数)。清单 4-9 提供了一个演示。

清单 4-9 。定义数值

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var daysInWeek = 7;
        var pi = 3.14;
        var hexValue = 0xFFFF;
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

您不必指定使用哪种号码。您只需表达您需要的值,JavaScript 就会相应地执行。在清单中,我定义了一个整数值、一个浮点值,并在一个值前面加上了0x来表示一个十六进制值。(清单中没有控制台输出。)

创建对象

JavaScript 支持对象的概念,并且有不同的方法来创建它们。清单 4-10 给出了一个简单的例子。

清单 4-10 。创建一个对象

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = new Object();
        myData.name = "Adam";
        myData.weather = "sunny";

        console.log("Hello " + myData.name + ". ");
        console.log("Today is " + myData.weather + ".");
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

我通过调用new Object()创建一个对象,并将结果(新创建的对象)赋给一个名为myData的变量。一旦创建了对象,我就可以通过赋值来定义对象的属性,就像这样:

...
myData.name = "Adam";
...

在这个语句之前,我的对象没有名为name的属性。在语句执行之后,属性确实存在,并且它被赋予了值Adam。您可以通过将变量名和属性名与句点组合来读取属性值,如下所示:

...
console.log("Hello " +myData.name+ ". ");
...

清单的结果如下:

Hello Adam.
Today is sunny.

使用对象文字

您可以使用对象文字格式在一个步骤中定义一个对象及其属性。清单 4-11 展示了这是如何做到的。

清单 4-11 。使用对象文字格式

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny"
        };

        console.log("Hello " + myData.name + ". ");
        console.log("Today is " + myData.weather + ".");
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

使用冒号(:)将您要定义的每个属性与其值分开,使用逗号(,)将属性分开。效果与前面的示例相同,清单的结果如下:

Hello Adam.
Today is sunny.

使用函数作为方法

我最喜欢 JavaScript 的一个特性是可以向对象添加函数。定义在对象上的函数被称为方法。不知道为什么,我觉得这很优雅,让人赏心悦目。清单 4-12 展示了如何以这种方式添加方法。

清单 4-12 。向对象添加方法

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
            printMessages: function() {
                console.log("Hello " + this.name + ". ");
                console.log("Today is " + this.weather + ".");
            }
        };
        myData.printMessages();
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

在这个例子中,我用一个函数创建了一个名为printMessages 的方法。注意,为了引用对象定义的属性,我必须使用this关键字。当一个函数作为一个方法使用时,该函数通过特殊变量this被隐式传递给调用该方法的对象作为参数。清单的输出如下所示:

Hello Adam.
Today is sunny.

使用对象

一旦你创建了对象,你可以用它们做很多事情。在接下来的部分,我将描述在本书后面会有用的活动。

读取和修改属性值

对对象做的最明显的事情是读取或修改分配给对象定义的属性的值。您可以使用两种不同的语法风格,这两种风格都显示在清单 4-13 中。

清单 4-13 。读取和修改对象属性

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
        };

        myData.name = "Joe";
        myData["weather"] = "raining";

        console.log("Hello " + myData.name+ ".");
        console.log("It is " + myData["weather"]);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

第一种风格是大多数程序员熟悉的,也是我在前面的例子中使用的。用句点将对象名和属性名连接在一起,如下所示:

...
myData.name = "Joe";
...

您可以使用等号(=)为属性指定一个新值,或者忽略当前值来读取当前值。第二种样式是数组样式的索引,如下所示:

...
myData["weather"] = "raining";
...

在这种样式中,您可以在方括号([])之间指定您想要的属性的名称。这是访问属性的一种便捷方式,因为您可以将感兴趣的属性的名称作为变量传递,如下所示:

...
var myData = {
    name: "Adam",
    weather: "sunny",
};

var propName = "weather";
myData[propName] = "raining";
...

这是如何枚举一个对象的属性的基础,我将在下面描述。下面是清单中的控制台输出:

Hello Joe.
It is raining

枚举对象的属性

使用for...in语句枚举一个对象拥有的属性。清单 4-14 展示了如何使用这个语句。

清单 4-14 。枚举对象的属性

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
            printMessages: function() {
                console.log("Hello " + this.name + ". ");
                console.log("Today is " + this.weather + ".");
            }
        };

        for (var prop in myData) {
            console.log("Name: " + prop + " Value: " + myData[prop]);
        }

    </script>
</head>
<body>
    This is a simple example
</body>
</html>

for...in循环在代码块中为myData对象中的每个属性执行语句。在每次迭代中,prop变量被赋予被处理的属性的名称。我使用数组索引样式从对象中检索属性值。该清单的输出如下所示(为了便于阅读,我对结果进行了格式化):

Name: name Value: Adam
Name: weather Value: sunny
Name: printMessages Value: function () {
    console.log("Hello " + this.name + ". ");
    console.log("Today is " + this.weather + ".");
}

从结果可以看出,我定义为方法的函数也被枚举了。这是 JavaScript 灵活处理函数的结果。

添加和删除属性和方法

即使使用了 object literal 样式,您仍然可以为对象定义新的属性。清单 4-15 给出了一个演示。(本节中的清单不产生任何控制台输出。)

清单 4-15 。向对象添加新属性

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
        };

        myData.dayOfWeek = "Monday";
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

在这个清单中,我向名为dayOfWeek的对象添加了一个新属性。我使用了点符号(用句点连接对象和属性名),但是我也可以使用索引样式的符号。正如你现在所期望的,你也可以通过将一个属性的值设置为一个函数来给一个对象添加新的方法,如清单 4-16 所示。

清单 4-16 。向对象添加新方法

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
        };

        myData.SayHello = function() {
          console.write("Hello");
        };
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

你可以使用delete关键字从一个对象中删除一个属性或方法,如清单 4-17 所示。

清单 4-17 。从对象中删除属性

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
        };

        delete myData.name;
        delete myData["weather"];
        delete myData.SayHello;
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

确定对象是否具有属性

你可以使用in表达式检查一个对象是否有属性,如清单 4-18 所示。

清单 4-18 。检查对象是否具有属性

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
        };

        var hasName = "name" in myData;
        var hasDate = "date" in myData;

        console.log("HasName: " + hasName);
        console.log("HasDate: " + hasDate);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

在这个例子中,我测试了一个存在的属性和一个不存在的属性。变量hasName的值将是true,属性hasDate的值将是false,如下所示:

HasName: true
HasDate: false

使用 JavaScript 运算符

JavaScript 定义了一组非常标准的操作符。我在表 4-1 中总结了最有用的。

表 4-1 。有用的 JavaScript 操作符

操作员 描述
++, -- 前或后递增和递减
+, -, *, /, % 加法、减法、乘法、除法、余数
<, <=, >, >= 小于,小于等于,大于,大于等于
==, != 平等和不平等测试
===, !== 同一性和非同一性测试
&&, &#124;&#124; 逻辑 AND 和 OR
= 分配
+ 串并置
?: 三操作数条件语句

使用条件语句

许多 JavaScript 操作符与条件语句一起使用。在本书中,我倾向于使用if/elseswitch语句。清单 4-19 展示了两者的用法(如果你使用过几乎任何一种编程语言,你都会很熟悉)。

清单 4-19 。使用 if/else 和 switch 条件语句

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var name = "Adam";

        if (name == "Adam") {
            console.log("Name is Adam");
        } else if (name == "Jacqui") {
            console.log("Name is Jacqui");
        } else {
            console.log("Name is neither Adam or Jacqui");
        }

        switch (name) {
            case "Adam":
                console.log("Name is Adam");
                break;
            case "Jacqui":
                console.log("Name is Jacqui");
                break;
            default:
                console.log("Name is neither Adam or Jacqui");
                break;
        }
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

清单中的结果如下:

Name is Adam
Name is Adam

相等运算符与相同运算符

等式和等式操作符特别值得注意。相等运算符将尝试将操作数强制为同一类型,以便评估相等性。这是一个方便的特性,只要你意识到它正在发生。清单 4-20 展示了等式操作符的作用。

清单 4-20 。使用相等运算符

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

       var firstVal = 5;
       var secondVal = "5";

        if (firstVal == secondVal) {
            console.log("They are the same");
        } else {
            console.log("They are NOT the same");
        }
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该脚本的输出如下:

They are the same

JavaScript 将两个操作数转换成相同的类型,并对它们进行比较。本质上,相等运算符测试值是否相同,而不管它们的类型如何。如果你想测试确保值的类型是相同的,那么你需要使用恒等运算符(===,三个等号,而不是两个等号),如清单 4- 21 所示。

清单 4-21 。使用标识运算符

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

       var firstVal = 5;
       var secondVal = "5";

        if (firstVal === secondVal) {
            console.log("They are the same");
        } else {
            console.log("They are NOT the same");
        }
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

在本例中,identity 运算符将认为这两个变量是不同的。该运算符不强制类型。该脚本的结果如下:

They are NOT the same

JavaScript 原语通过值进行比较,但是 JavaScript 对象通过引用进行比较。清单 4-22 展示了 JavaScript 如何处理对象的相等和相同测试。

清单 4-22 。对对象执行相等和相同测试

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var myData1 = {
            name: "Adam",
            weather: "sunny",
        };

        var myData2 = {
            name: "Adam",
            weather: "sunny",
        };

        var myData3 = myData2;

        var test1 = myData1 == myData2;
        var test2 = myData2 == myData3;
        var test3 = myData1 === myData2;
        var test4 = myData2 === myData3;

        console.log("Test 1: " + test1 + " Test 2: " + test2);
        console.log("Test 3: " + test3 + " Test 4: " + test4);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该脚本的结果如下:

Test 1: false Test 2: true
Test 3: false Test 4: true

清单 4-23 显示了对原语执行的相同测试。

清单 4-23 。对对象执行相等和相同测试

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var myData1 = 5;
        var myData2 = "5";
        var myData3 = myData2;

        var test1 = myData1 == myData2;
        var test2 = myData2 == myData3;
        var test3 = myData1 === myData2;
        var test4 = myData2 === myData3;

        console.log("Test 1: " + test1 + " Test 2: " + test2);
        console.log("Test 3: " + test3 + " Test 4: " + test4);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该脚本的结果如下:

Test 1: true Test 2: true
Test 3: false Test 4: true

显式转换类型

字符串连接操作符 ( +)的优先级高于加法操作符(还有+),这意味着 JavaScript 将优先于加法操作连接变量。这可能会造成混乱,因为 JavaScript 也会自由地转换类型来产生结果——而不总是预期的结果,如清单 4-24 所示。

清单 4-24 。字符串连接运算符优先级

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var myData1 = 5 + 5;
        var myData2 = 5 + "5";

        console.log("Result 1: " + myData1);
        console.log("Result 2: " + myData2);

    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该脚本的结果如下:

Result 1: 10
Result 2: 55

第二种结果是引起混乱的那种。通过运算符优先级和过急类型转换的组合,原本应该是加法运算的操作被解释为字符串串联。为了避免这种情况,可以显式转换值的类型,以确保执行正确的操作,如以下部分所述。

将数字转换为字符串

如果你正在处理多个数字变量,并想把它们连接成字符串,那么你可以用toString方法 把数字转换成字符串,如清单 4-25 所示。

清单 4-25 。使用 Number.toString 方法

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData1 = (5).toString() + String(5);
        console.log("Result: " + myData1);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

请注意,我将数值放在了括号中,然后调用了toString方法。这是因为在调用number类型定义的方法之前,您必须允许 JavaScript 将文字值转换成number。我还展示了实现相同效果的另一种方法,即调用String函数,并将数值作为参数传入。这两种技术具有相同的效果,都是将一个number转换成一个string,这意味着+操作符用于字符串连接而不是加法。该脚本的输出如下:

Result: 55

还有一些其他的方法可以让你更好地控制一个数字如何被表示成一个字符串。我在表 4-2 中简要描述了这些。表格中显示的所有方法都由number类型定义。

表 4-2 。有用的 Number.toString 方法

方法 描述 返回
toString() 代表基数为 10 的数字 string
toString(2)``toString(8) 用二进制、八进制或十六进制表示法表示一个数 string
toFixed(n) 表示小数点后有 n 位的实数 string
toExponential(n) 使用指数表示法表示一个数字,小数点前有一位,小数点后有 n 位 string
toPrecision(n) 代表一个有 n 个有效数字的数字,如果需要,使用指数符号 string

将字符串转换为数字

补充技术是将字符串转换成数字,这样您就可以执行加法而不是连接。你可以用Number函数来完成,如清单 4-26 所示。

清单 4-26 。将字符串转换为数字

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var firstVal = "5";
        var secondVal = "5";

var 结果=数量(firstVal) +数量(second val);

        console.log("Result: " + result);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该脚本的输出如下:

Result: 10

Number方法在解析字符串值的方式上非常严格,但是你可以使用另外两个更灵活的函数,它们会忽略后面的非数字字符。这些功能是parseIntparseFloat。我已经在表 4-3 中描述了所有三种方法。

表 4-3 。对数字方法有用的字符串

方法 描述
Number(str) 分析指定的字符串以创建整数值或实数值
parseInt(str) 分析指定的字符串以创建整数值
parseFloat(str) 分析指定的字符串以创建整数值或实数值

使用数组

JavaScript 数组的工作方式很像大多数其他编程语言中的数组。清单 4-27 展示了如何创建和填充一个数组。

清单 4-27 。创建并填充数组

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var myArray = new Array();
        myArray[0] = 100;
        myArray[1] = "Adam";
        myArray[2] = true;

    </script>
</head>
<body>
    This is a simple example
</body>
</html>

我通过调用new Array()创建了一个新数组。这创建了一个空数组,我将它赋给了变量myArray。在随后的语句中,我为数组中的不同索引位置赋值。(这个清单中没有控制台输出。)()

在这个例子中有一些事情需要注意。首先,在创建数组时,我不需要声明数组中的项数。JavaScript 数组会自动调整大小以容纳任意数量的项目。第二点是,我不必声明数组将保存的数据类型。任何 JavaScript 数组都可以包含任何混合的数据类型。在这个例子中,我给数组分配了三个项目:一个number、一个string和一个boolean

使用数组文本

数组字面样式让你在一条语句中创建并填充一个数组,如清单 4-28 所示。

清单 4-28 。使用数组文字样式

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

      var myArray = [100, "Adam", true];

    </script>
</head>
<body>
    This is a simple example
</body>
</html>

在这个例子中,我通过在方括号([])之间指定我想要的数组中的项目,指定了应该给myArray变量分配一个新的数组。(这个清单中没有控制台输出。)

读取和修改数组的内容

使用方括号([])读取给定索引处的值,将所需的索引放在括号之间,如清单 4-29 所示。

清单 4-29 。从数组索引中读取数据

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myArray = [100, "Adam", true];
        console.log("Index 0: " +myArray[0]);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

只需给索引赋值,就可以修改 JavaScript 数组中任何位置的数据。就像常规变量一样,您可以在索引处切换数据类型,不会有任何问题。清单的输出如下所示:

Index 0: 100

清单 4-30 展示了如何修改一个数组的内容。

清单 4-30 。修改数组的内容

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myArray = [100, "Adam", true];
        myArray[0] = "Tuesday";
        console.log("Index 0: " + myArray[0]);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

在这个例子中,我将一个string赋值给数组中的位置0,这个位置以前是由一个number持有的,并产生以下输出:

Index 0: Tuesday

枚举数组的内容

使用循环枚举数组的内容。清单 4-31 展示了如何应用循环来显示一个简单数组的内容。

清单 4-31 。枚举数组的内容

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myArray = [100, "Adam", true];
        for (var i = 0; i < myArray.length; i++) {
            console.log("Index " + i + ": " + myArray[i]);
        }
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

JavaScript 循环的工作方式与许多其他语言中的循环一样。使用length属性确定数组中有多少个元素。清单的输出如下所示:

Index 0: 100
Index 1: Adam
Index 2: true

使用内置数组方法

JavaScript Array对象定义了许多可以用来处理数组的方法。表 4-4 描述了这些方法中最有用的。

表 4-4 。有用的数组方法

方法 描述 返回
concat(otherArray) 将数组的内容与参数指定的数组连接起来。可以指定多个数组。 Array
join(separator) 连接数组中的所有元素,形成一个字符串。该参数指定用于分隔各项的字符。 string
pop() 将数组视为堆栈,移除并返回数组中的最后一项。 object
push(item) 将数组视为堆栈,并将指定项追加到数组中。 void
reverse() 反转数组中项目的顺序。 Array
shift() 类似于 pop,但是对数组中的第一个元素进行操作。 object
slice(start,end) 返回数组的一部分。 Array
sort() 对数组中的项目进行排序。 Array
splice(index, count) 从指定的索引开始,从数组中删除 count 个项目。 Array
unshift(item) 类似于 push,但是在数组的开头插入新元素。 void

处理错误

JavaScript 使用try...catch语句来处理错误。在很大程度上,我不会担心本书中的错误,因为我的重点是解释 jQuery 的特性,而不是一般的编程技术。清单 4-32 展示了如何使用这种语句。

清单 4-32 。处理异常

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        try {
            var myArray;
            for (var i = 0; i < myArray.length; i++) {
                console.log("Index " + i + ": " + myArray[i]);
            }
        } catch (e) {
            console.log("Error: " + e);
        }
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

这个脚本中的问题很常见。我正在尝试使用一个没有正确初始化的变量。我已经包装了我怀疑会导致语句的try子句出错的代码。如果没有出现问题,那么语句会正常执行,并且会忽略catch子句。

但是,由于该代码中有一个错误,因此try子句中语句的执行会立即停止,控制权会传递给catch子句,并在控制台上产生以下输出:

Error: TypeError: Cannot read property 'length' of undefined

您遇到的错误由一个Error对象描述,该对象被传递给catch子句。表 4-5 显示了由Error对象定义的属性。

表 4-5 。错误对象

财产 描述 返回
message 错误情况的描述。 string
name 错误的名称。默认情况下,这是错误 string
number 这种错误的错误号(如果有)。 number

catch条款是你从错误中恢复或清理的机会。如果有需要执行的语句,不管是否有错误,你可以把它们放在可选的finally子句中,如清单 4-33 所示。

清单 4-33 。使用 finally 子句

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        try {
            var myArray;
            for (var i = 0; i < myArray.length; i++) {
                console.log("Index " + i + ": " + myArray[i]);
            }
        } catch (e) {
            console.log("Error: " + e);
        } finally {
            console.log("Statements here are always executed");
        }
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该清单产生以下控制台输出:

Error: TypeError: Cannot read property 'length' of undefined
Statements here are always executed

比较未定义值和空值

JavaScript 定义了几个特殊值,在比较它们时需要小心:undefinednull。当你读取一个没有赋值的变量或者试图读取一个不存在的对象属性时,就会返回undefined值。清单 4-34 展示了undefined在 JavaScript 中的用法。

清单 4-34 。未定义的特殊值

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">
        var myData = {
            name: "Adam",
            weather: "sunny",
        };
        console.log("Prop: " +myData.doesntexist);
    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该清单的输出如下:

Prop: undefined

JavaScript 的不同寻常之处在于它还定义了另一个特殊值nullnull值与undefined值略有不同。当没有定义值时,返回undefined值,当你想表示你已经赋值,但该值不是有效的objectstringnumberboolean时,使用null;也就是说,你定义了一个值没有值。为了帮助澄清这一点,清单 4-35 显示了从undefinednull的过渡。

清单 4-35 。使用 undefined 和 null

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var myData = {
            name: "Adam",
        };

        console.log("Var: " + myData.weather);
        console.log("Prop: " + ("weather" in myData));

        myData.weather = "sunny";
        console.log("Var: " + myData.weather);
        console.log("Prop: " + ("weather" in myData));

        myData.weather = null;
        console.log("Var: " + myData.weather);
        console.log("Prop: " + ("weather" in myData));

    </script>
</head>
<body>
    This is a simple example
</body>
</html>

我创建了一个对象,然后尝试读取未定义的属性weather的值:

...
console.log("Var: " + myData.weather);
console.log("Prop: " + ("weather" in myData));
...

没有weather属性,所以调用myData.weather返回的值是undefined,使用in关键字判断对象是否包含属性返回false。这两条语句的输出如下:

Var: undefined
Prop: false

接下来,我给weather属性赋值,这样做的效果是将属性添加到对象中:

...
myData.weather = "sunny";
console.log("Var: " + myData.weather);
console.log("Prop: " + ("weather" in myData));
...

我读取属性的值,并再次检查对象中是否存在该属性。如您所料,对象定义了属性,其值为sunny:

Var: sunny
Prop: true

现在我将属性的值设置为null,如下所示:

...
myData.weather = null;
...

这有特定的效果。属性仍然由对象定义,但是我已经指出它不包含值。当我再次执行检查时,我得到以下结果:

Var: null
Prop: true

在比较undefinednull值时,这种区别很重要,因为null是一个object,而undefined本身就是一个类型。

检查是否为空或未定义

如果你想检查一个属性是null还是undefined(你不在乎哪个),那么你可以简单地使用一个if语句和一个否定运算符(!,如清单 4-36 所示。

清单 4-36 。检查属性是否为空或未定义

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var myData = {
            name: "Adam",
            city: null
        };

        if (!myData.name) {
            console.log("name IS null or undefined");
        } else {
            console.log("name is NOT null or undefined");
        }

        if (!myData.city) {
            console.log("city IS null or undefined");
        } else {
            console.log("city is NOT null or undefined");
        }

    </script>
</head>
<body>
    This is a simple example
</body>
</html>

这种技术依赖于 JavaScript 执行的类型强制,因此您检查的值被视为boolean值。如果一个变量或属性是nullundefined,那么被强制的boolean值就是false。清单产生以下输出:

name is NOT null or undefined
city IS null or undefined

区分空值和未定义值

如果你想比较两个值,你可以选择。如果您想将一个undefined值视为与一个null值相同,那么您可以使用等号运算符(==)并依靠 JavaScript 来转换类型。例如,undefined变量将被视为等于null变量。如果你想区分nullundefined,那么你需要使用恒等运算符(===)。清单 4-37 显示了两种比较。

清单 4-37 。空值和未定义值的相等和相同比较

<!DOCTYPE HTML>
<html>
<head>
    <title>Example</title>
    <script type="text/javascript">

        var firstVal = null;
        var secondVal;

        var equality = firstVal == secondVal;
        var identity = firstVal === secondVal;

        console.log("Equality: " + equality);
        console.log("Identity: " + identity);

    </script>
</head>
<body>
    This is a simple example
</body>
</html>

该脚本的输出如下:

Equality: true
Identity: false

摘要

在这一章中,我向您展示了将在本书中使用的核心 JavaScript 特性。理解基本的 JavaScript 是使用 jQuery 的基础,您将在前面的章节中看到。在本书的第二部分,我将正确地介绍 jQuery,并向您展示如何使用它。

五、jQuery 基础知识

在这一章中,我将向你介绍你的第一个 jQuery 脚本。这个脚本很简单,但是它演示了 jQuery 的许多最重要的特性,包括如何在文档中选择元素、如何向您呈现这些选择,以及 jQuery 和内置 DOM API(HTML 规范的一部分)之间的关系的本质。表 5-1 对本章进行了总结。

表 5-1 。章节总结

问题 解决办法 列表
将 jQuery 添加到 HTML 文档。 使用link元素导入 jQuery 元素,链接到您的 web 服务器或 CDN。添加一个script元素来定义您的 jQuery 脚本。 one
动态选择 jQuery 1.x 或 2.x 行。 使用条件注释。 Two
选择文档中的元素。 将 CSS 选择器传递给` 问题
--- --- ---
将 jQuery 添加到 HTML 文档。 使用link元素导入 jQuery 元素,链接到您的 web 服务器或 CDN。添加一个script元素来定义您的 jQuery 脚本。 one
动态选择 jQuery 1.x 或 2.x 行。 使用条件注释。 Two
jQuery函数。 3, 4, 10
重命名` 问题 解决办法
--- --- ---
将 jQuery 添加到 HTML 文档。 使用link元素导入 jQuery 元素,链接到您的 web 服务器或 CDN。添加一个script元素来定义您的 jQuery 脚本。 one
动态选择 jQuery 1.x 或 2.x 行。 使用条件注释。 Two
功能。 使用noConflict方法。 5, 6
推迟 jQuery 脚本的执行,直到文档加载完毕。 在全局document变量上注册一个ready事件的处理程序,或者将一个函数传递给` 问题
--- --- ---
将 jQuery 添加到 HTML 文档。 使用link元素导入 jQuery 元素,链接到您的 web 服务器或 CDN。添加一个script元素来定义您的 jQuery 脚本。 one
动态选择 jQuery 1.x 或 2.x 行。 使用条件注释。 Two
函数。 7, 8
控制何时触发就绪事件。 使用holdReady事件。 nine
将元素选择限制在文档的一部分。 将上下文传递给$函数。 11, 12
确定用于创建jQuery对象的上下文。 读取context属性。 Thirteen
HTMLElement对象创建一个jQuery对象。 HTMLElement对象作为参数传递给$函数。 Fourteen
枚举一个jQuery对象的内容。 jQuery对象视为数组或使用each方法。 15, 16
jQuery元素中查找特定元素。 使用indexget方法。 17–19
对文档中的多个元素应用操作。 jQuery对象上使用 jQuery 方法。 Twenty
对一个jQuery对象应用多个操作。 将方法调用链接在一起。 21–23
处理一个事件。 使用 jQuery 事件处理程序方法之一。 Twenty-four

JQUERY 自上一版以来发生了变化

在本书的第一版中,我描述了如何使用 selector属性从 jQuery 中获取一个选择器字符串,该字符串可用于将来的重复查询。从 jQuery 1.9 开始,selector属性已经被弃用,不应该再使用。

jQuery 1.9/2.0 使用的选择器引擎(称为 Sizzle )实现了对一些新的 CSS3 选择器的支持,即使在不支持它们的浏览器中。选择器有::nth-last-child:nth-of-type:nth-last-of-type:first-of-type:last-of-type:only-of-type:target:root:lang.

设置 jQuery

对于 jQuery,您需要做的第一件事是将它添加到想要处理的文档中。清单 5-1 显示了您在第一部分中第一次看到的花店示例文档,添加了 jQuery 库。

清单 5-1 。花店示例文档

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div id="row2" class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

为了帮助保持对内容的关注,我将 CSS 样式移到了一个单独的样式表中,名为 styles.css,如第三章所示。您可以看到我是如何将 jQuery 库添加到文档的,如下所示:

...
<script src="jquery-2.0.2.js" type="text/javascript"></script>
...

一旦您选择了将要使用的 jQuery 行(1.x 或 2.x 行,如第三章中的所述),您将从jquery.com中选择两个文件——一个文件扩展名为.js,另一个文件扩展名为.min.js。对于我写这篇文章时的 2.x 行版本,这些文件被称为jquery-2.0.2.jsjquery-2.0.2.min.js

jquery-2.0.2.js文件是在网站或应用开发过程中通常使用的文件。这个文件大约 240KB,是标准的 JavaScript 代码。您可以打开并阅读该文件,了解 jQuery 是如何实现其特性的,并在代码中遇到问题时使用浏览器调试器来找出问题所在。

提示 jQuery 正在积极开发中,所以当你读到这篇文章时,一个更新的版本几乎肯定已经发布了。但是不要担心——尽管有很多不推荐的方法和分裂的开发路线,jQuery API 非常稳定,并且随着 jQuery 1.x 和 2.x 的不断成熟,我在本书中展示的所有技术都将继续发挥作用。

另一个文件jquery.2.0.2.min.js,是为用户部署站点或 web 应用时使用的。它包含相同的 JavaScript 代码,但是被缩减为,这意味着所有的空白字符都被删除,有意义的变量名被替换为单字符名称以节省空间。为了调试的目的,缩小的脚本几乎无法阅读,但是它要小得多。缩小过程将文件大小减少到大约 82KB,如果您提供大量依赖 jQuery 的页面,那么这种差异可以为您节省大量带宽。

image 提示你可以从jquery.com下载一个源地图(扩展名为.min.map)。源代码映射允许简化的代码更容易调试。它们是一个新的想法,在我写这篇文章的时候还没有被广泛实现。你可以在http://www.html5rocks.com/en/tutorials/developertools/sourcemaps看到一个简单的演示。

为 JQUERY 使用 CDN

将 jQuery 库存储在您自己的 web 服务器上的另一种方法是使用一个托管 jQuery 的公共 内容交付网络 (CDN) 。CDN 是一个由服务器组成的分布式网络,使用离用户最近的服务器向用户交付文件。使用 CDN 有几个好处。首先是用户体验更快,因为 jQuery 库文件是从离他们最近的服务器上下载的,而不是从您的服务器上。通常根本不需要下载:jQuery 如此受欢迎,以至于用户的浏览器可能已经从另一个使用 jQuery 的应用或网站缓存了这个库。第二个好处是没有任何带宽用于向用户交付 jQuery。对于高流量的网站来说,这可以节省大量的成本。

使用 CDN 的时候,一定要对 CDN 运营商有信心。您希望确保用户收到他们应该收到的文件,并且服务将始终可用。谷歌和微软都免费为 jQuery(以及其他流行的 JavaScript 库)提供 CDN 服务。两家公司都有运行高可用性服务的良好经验,不太可能故意篡改 jQuery 库。你可以在www.asp.net/ajaxlibrary/cdn.ashx了解微软服务,在http://code.google.com/apis/libraries/devguide.html了解谷歌服务。

CDN 方法不适合在内部网中交付给用户的应用,因为它会导致所有浏览器都通过互联网来获取 jQuery 库,而不是访问本地服务器,后者通常更近、更快、带宽成本更低。

使用条件注释

在清单 5-1 中,我只包含了版本 2.0.2 的 jQuery 库。这个版本提供了最好的性能,但它不支持旧版本的 Internet Explorer,如第一章所述。

好消息是,您不必在性能和兼容性之间做出选择。有一种技术可以自动在 1.x 和 2.x jQuery 库之间进行动态选择,使用一种称为条件注释的功能,这是微软为 Internet Explorer 5 创建的 HTML 的非标准增强。在清单 5-2 中,您可以看到我是如何将条件注释特性应用到示例 HTML 文档中的。

清单 5-2 。使用条件注释在 jQuery 1.x 和 2.x 之间进行动态选择

...
<head>
    <title>Example</title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <!--[if lt IE 9]>
        <script src="jquery-1.10.1.js" type="text/javascript"></script>
    <![endif]-->
    <!--[if gte IE 9]><!-->
        <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <!--<![endif]-->
</head>
...

条件注释将为 Internet Explorer 9 之前的版本加载 jQuery 1.10.1,为所有其他浏览器加载 jQuery 2.0.2。你应该注意准确地复制这些评论——很容易出错。

image 提示我不打算在本书中深入讨论条件注释,但你可以在http://en.wikipedia.org/wiki/Conditional_comment了解更多。

第一个 jQuery 脚本

现在我已经将 jQuery 库添加到了文档中,我可以编写一些使用 jQuery 功能的 JavaScript 了。清单 5-3 包含一个简单的script元素,展示了一些基本的 jQuery 特性。

清单 5-3 。第一个 jQuery 脚本

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            $("img:odd").mouseenter(function (e) {
                $(this).css("opacity", 0.5);
            }).mouseout(function (e) {
                $(this).css("opacity", 1.0);
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div id="row2" class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

这是一个简短的脚本,但是它展示了 jQuery 的一些最重要的特性和特征。在这一章中,我将一行一行地分解这个脚本,但是这本书的其余部分将会让你完全理解这个脚本所涉及的所有功能领域。首先,图 5-1 显示了这个脚本创建的效果。

9781430263883_Fig05-01.jpg

图 5-1 。更改图像不透明度

image 提示您会注意到,我已经在清单中返回到了 jQuery 2.0.2 的显式使用——我将在本书的其余大部分中这样做。我想让例子简单明了,但是我建议您使用条件注释技术,我发现这在我自己的项目中很有用。

当鼠标移动到水仙花、牡丹和雪花莲图像上时,该脚本会改变它们的不透明度。这具有使图像看起来更亮和褪色的效果。当鼠标离开图像时,不透明度会恢复到其先前的值。紫菀、玫瑰和报春花的图像不受影响。

了解 jQuery $函数

您可以通过$(...)函数访问 jQuery,为了简单起见,我将它称为 *\(函数*。`\)函数是 jQuery 奇妙世界的入口点,也是jQuery`函数的简写。如果您愿意,您可以重写示例脚本以使用完整的函数名,如清单 5-4 所示。

清单 5-4 。使用 jQuery 函数代替简写

...
<script type="text/javascript">
    jQuery(document).ready(function () {
        jQuery("img:odd").mouseenter(function(e) {
           jQuery(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           jQuery(this).css("opacity", 1.0);
        });
    });
</script>
...

该脚本提供了与上一个示例相同的功能。它需要的输入稍微多一点,但是它的优点是可以显式地使用 jQuery。

这可能很有用,因为 jQuery 不是唯一使用$符号的 JavaScript 库,当您在同一个文档中使用多个库时,这可能会导致问题。您可以通过调用jQuery.noConflict方法让 jQuery 放弃对$的控制,如清单 5-5 中的所示。

清单 5-5 。释放 jQuery 对$的控制

...
<script type="text/javascript">
    jQuery.noConflict();
    jQuery(document).ready(function () {
        jQuery("img:odd").mouseenter(function(e) {
           jQuery(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           jQuery(this).css("opacity", 1.0);
        });
    });
</script>
...

您也可以定义自己的速记符号。你可以通过将noConflict方法的结果赋给一个变量来实现,如清单 5-6 所示。

清单 5-6 。使用另一种速记法

...
<script type="text/javascript">
    var jq = jQuery.noConflict();
    jq(document).ready(function () {
        jq("img:odd").mouseenter(function(e) {
           jq(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           jq(this).css("opacity", 1.0);
        });
    });
</script>
...

在这个例子中,我创建了自己的速记,jq,然后在我的脚本的其余部分使用这个速记。

image 提示我将在本书中通篇使用$符号,因为这是 jQuery 的常规约定(也因为我不会使用任何其他想要控制$的库)。

不管你如何引用主jQuery函数,你都可以传递相同的一组参数,其中最重要的我已经在表 5-2 中描述过了。所有这些论点都将在本章后面描述,除了最后一个,它将在第七章中描述。

表 5-2 。主 jQuery 函数的参数

争吵 描述
$(function) 指定当 DOM 准备好时要执行的函数。
$(selector) $(selector, context) 从文档中选择元素。
$(HTMLElement) $(HTMLElement[]) 从一个HTMLElement或一组HTMLElement对象创建一个 jQuery 对象。
$() 创建一个空选择。
$(HTML) $(HTML, map) 使用可选的 map 对象从 HTML 片段创建新元素来定义属性。详见第七章。

等待文档对象模型

在第二章的中,我将script元素放在了文档的末尾,这样浏览器在执行我的 JavaScript 代码之前就会在 DOM 中创建所有的对象。通过使用 jQuery,通过使用下面的技术,您可以巧妙地避免这个问题:

...
<script type="text/javascript">
    $(document).ready(function () {
        // ...
*code to execute*...
    });
</script>
...

我没有将 JavaScript 语句直接放在script元素中,而是将document对象(我在《??》第一章中介绍过)传递给了$函数,并调用了ready方法,传递了一个我希望只有当浏览器加载完 HTML 文档中的所有内容时才执行的函数。清单 5-7 显示了我如何在示例 HTML 文档中应用这一技术。

清单 5-7 。等待 DOM

...
<script type="text/javascript">
    $(document).ready(function () {
        $("img:odd").mouseenter(function (e) {
            $(this).css("opacity", 0.5);
        }).mouseout(function (e) {
            $(this).css("opacity", 1.0);
        });
    });
</script>
...

使用 ready函数意味着我可以将script元素放在 HTML 文档中我想要的任何地方,因为 jQuery 会防止函数过早执行。我喜欢把我的script元素放在 HTML 文档的head元素中,但这只是我的偏好。

image 注意ready方法传递一个function会为 jQuery ready事件创建一个处理程序。我在第九章中全面介绍了 jQuery 事件。目前,请接受这样的事实:当文档被加载并且 DOM 准备好可以使用时,您传递给ready方法的function将被调用。

忘记功能

一个常见的错误是省略这个咒语的function部分,只将一系列 JavaScript 语句传递给ready方法。这是行不通的,浏览器会立即执行这些语句,而不是在 DOM 准备好的时候。考虑以下脚本:

...
<script type="text/javascript">
    function countImgElements() {
        return $("img").length;
    }
    $(document).ready(function() {
      console.log("Ready function invoked. IMG count: " + countImgElements());
    });
    $(document).ready(
      console.log("Ready statement invoked. IMG count: " + countImgElements())
    );
</script>

我调用了两次ready方法,一次用了一个function,另一次只是传入了一个常规的 JavaScript 语句。在这两种情况下,我都调用了 countImgElements函数,该函数返回 DOM 中存在的img元素的数量。(暂时不要担心这个方法如何运作。我将在本章后面解释对length属性的调用。)当我加载文档时,脚本被执行,以下输出被写入控制台:

Ready statement invoked. IMG count: 0

正如您所看到的,在加载文档时,在浏览器发现文档中的img元素并创建相应的 DOM 对象之前,执行没有函数的语句。

使用替代符号

如果愿意,可以将函数作为参数传递给 jQuery $函数。这与使用$(document).ready方法具有相同的效果。清单 5-8 提供了一个演示。

清单 5-8 。推迟函数的执行,直到 DOM 准备好

...
<script type="text/javascript">
    $(function() {
        $("img:odd").mouseenter(function(e) {
           $(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           $(this).css("opacity", 1.0);
        })
    });
</script>
...

推迟就绪事件

您可以通过使用holdReady方法来控制何时触发ready事件。如果您需要动态加载外部资源(一种不常见的高级技术),这可能会很有用。在触发ready事件之前,必须调用holdReady方法,当您准备好时,可以再次调用该方法。清单 5-9 给出了一个使用这种方法的例子。

清单 5-9 。使用保持就绪方法

...
<script type="text/javascript">

    $.holdReady(true);

    $(document).ready(function() {
        console.log("Ready event triggered");
        $("img:odd").mouseenter(function(e) {
           $(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           $(this).css("opacity", 1.0);
        })
    });

    setTimeout(function() {
        console.log("Releasing hold");
        $.holdReady(false);
    }, 5000);

</script>
...

我在script元素的开头调用了holdReady方法,传入true作为参数,表示我希望举行ready事件。然后我定义了当ready事件被触发时我希望被调用的函数(这是我打开这一章时使用的同一套语句,它改变了一些图像的不透明度)。

最后,我使用setTimeout方法在五秒钟后调用一个函数。这个函数调用带有参数falseholdReady方法,它告诉 jQuery 触发ready事件。最终结果是我将ready事件延迟了五秒钟。我添加了一些调试消息,当文档加载到浏览器中时,这些消息会将以下输出写入控制台:

Releasing hold
Ready event triggered

image 提示你可以多次调用holdReady方法,但是在ready事件被触发之前,带有true参数的holdReady方法的调用次数必须与带有false参数的调用次数相等。

选择元素

jQuery 功能最重要的一个方面是如何从 DOM 中选择元素。在示例脚本中,我定位了所有的奇数 img元素,如清单 5-10 中的所示。

清单 5-10 。从 DOM 中选择元素

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img:odd").mouseenter(function(e) {
           $(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           $(this).css("opacity", 1.0);
        })
    });
</script>
...

要选择元素,只需将选择器传递给$函数。jQuery 支持我在第三章中描述的所有 CSS 选择器,加上一些额外的选择器,这些选择器给你一些方便的细粒度控制。在这个例子中,我使用了:odd伪选择器,它选择与选择器主要部分匹配的奇数元素(在这个例子中是img,它选择所有的img元素,如第三章所述)。:odd选择器是从零开始的,这意味着第一个元素被认为是偶数。一开始可能会有点困惑。表 5-3 列出了最有用的 jQuery 选择器。

表 5-3 。jQuery 扩展选择器

选择器 描述
:animated 选择所有正在制作动画的元素。
:contains(text) 选择包含指定文本的元素。
:eq(n) 选择第n个索引处的元素(从零开始)。
:even 选择所有事件编号的元素(从零开始)。
:first 选择第一个匹配的元素。
:gt(n) 选择索引大于n(从零开始)的所有元素。
:has(selector) 选择至少包含一个与指定选择器匹配的元素的元素。
:last 选择最后匹配的元素。
:lt(n) 选择索引小于n(从零开始)的所有元素。
:odd 选择所有奇数元素(从零开始)。
:text 选择所有文本元素。

image 提示你可以通过调用不带任何参数的$函数($())来创建一个空的选择。我提到这一点是为了完整性,但这并不是我曾经有理由使用的一个特性。

我称之为最有用的,因为它们定义了使用 CSS 选择器很难重新创建的功能。这些选择器就像 CSS 伪选择器一样使用。它们可以单独使用,在这种情况下,它们应用于 DOM 中的所有元素,如下所示:

...
$(":even")
...

或者与其他选择器组合以缩小选择范围,如下所示:

...
$("img:even")
...

jQuery 还定义了基于类型选择元素的选择器,如表 5-4 所述。

表 5-4 。jQuery 类型扩展选择器

选择器 描述
:button 选择所有按钮。
:checkbox 选中所有复选框。
:file 选择所有文件元素。
:header 选择所有标题元素(h1h2等)。
:hidden 选择所有隐藏元素。
:image 选择所有图像元素。
:input 选择所有输入元素。
:last 选择最后匹配的元素。
:parent 选择其他元素的所有父元素。
:password 选择所有密码元素。
:radio 选择所有单选按钮。
:reset 选择重置表单的所有元素。
:selected 选择所有选定的元素。
:submit 选择所有表单提交元素。
:visible 选择所有可见元素。

考虑选择器性能

如果您花时间阅读 jQuery,您肯定会遇到关于选择器性能的讨论。许多人花费大量时间比较不同的选择器表达方式,以最大限度地发挥 jQuery 的性能。

我的观点很简单:这不应该有关系——如果有关系,那就是其他问题的迹象。jQuery 的性能非常好,尤其是在最新版本的主流浏览器上,这些浏览器具有快速的 JavaScript 实现。当我看到项目团队试图优化选择器性能时,通常是因为他们正在处理大量的 HTML 元素,在这些元素中,执行一个选择需要数百毫秒——当执行一个操作需要几个这样的选择时,我们开始进入用户注意到的延迟长度。

真正的问题不是 jQuery——而是向浏览器发送的内容超出了合理预期的范围。浏览器的功能越来越强大,但是它们的功能是有限的,尤其是老版本的浏览器和运行在移动设备上的浏览器。

如果你发现自己很难足够快地选择元素,不要试图优化选择器的使用。相反,重新考虑你对 HTML 元素的使用:想办法尽量减少发送到浏览器的内容,在服务器上承担一些处理工作,不要把 web 应用当作桌面应用。

用上下文缩小选择范围

默认情况下,jQuery 在整个 DOM 中搜索元素,但是您可以通过向$函数提供一个额外的参数来缩小选择范围。这给了搜索一个上下文,它被用作匹配元素的起点,如清单 5-11 所示。

清单 5-11 。使用上下文缩小搜索范围

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

        $("img:odd",$(".drow")).mouseenter(function(e) {
           $(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           $(this).css("opacity", 1.0);
        })
    });
</script>
...

在这个例子中,我使用一个 jQuery 选择作为另一个的上下文。首先评估上下文,它匹配所有属于drow类的元素。这组元素然后被用作img:odd选择器的上下文。

当您提供包含多个元素的上下文时,每个元素都被用作搜索的起点。这种方法有一个有趣的微妙之处。匹配上下文的元素被收集在一起,然后执行主选择。在本例中,这意味着img:odd选择器应用于drow选择器的结果,这意味着奇数编号的元素与搜索整个文档时的元素不同。最终结果是不透明效果被应用到drow类的每个div元素中奇数编号的img元素,选择水仙花和报春花图像。当我在前面的例子中省略上下文时,效果被应用到水仙花、牡丹和雪花莲图像上。

如果您只想匹配文档中从给定点开始的元素,那么您可以使用一个HTMLElement对象作为上下文。清单 5-12 包含了一个例子。在下一节中,我将向您展示如何在 jQuery 世界和HTMLElement对象之间轻松切换。

清单 5-12 。使用 HTMLElement 作为上下文

...
<script type="text/javascript">
    $(document).ready(function() {
        var elem = document.getElementById("oblock");

        $("img:odd",elem).mouseenter(function(e) {
           $(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           $(this).css("opacity", 1.0);
        })
    });
</script>
...

本例中的脚本搜索奇数编号的img元素,将搜索限制在那些从idoblock的元素派生的元素。

当然,使用后代 CSS 选择器也可以达到同样的效果。当您希望以编程方式缩小搜索范围,而不必构造选择器字符串时,这种方法的好处就显现出来了。这种情况的一个很好的例子是在处理事件时。你可以在第九章的中了解更多关于事件的信息(并看看HTMLElement物体在这种情况下是如何出现的)。

了解选择结果

当您使用 jQuery 从 DOM 中选择元素时,$函数的结果是一个容易混淆的名称为jQuery的对象,它表示零个或多个 DOM 元素。事实上,当您执行一个修改一个或多个元素的 jQuery 操作时,结果很可能是一个jQuery对象,这是一个重要的特征,我将很快返回。

由 jQuery 对象定义的方法和属性基本上是本书其余部分的内容,但我可以在本章中介绍一些基本成员,如表 5-5 中所述。

表 5-5 。基本 jQuery 对象成员

选择器 描述 返回
context 返回用作搜索上下文的元素集。 HTMLElement
each(function) 对每个选定的元素执行该功能。 jQuery
get(index) 获取指定索引处的HTMLElement对象。 HTMLElement
index(HTMLElement) 返回指定HTMLElement.的索引 number
index(jQuery) 返回jQuery对象中第一个元素的索引。 number
index(selector) 返回选择器匹配的元素集中第一个元素在jQuery对象中的索引 number
length 返回jQuery对象包含的元素数量。 number
size() 返回jQuery对象中元素的数量。 number
toArray() 以数组形式返回由jQuery对象包含的HTMLElement对象。 HTMLElement[]

确定上下文

context 属性提供了创建 jQuery 时使用的上下文的详细信息。如果单个HTMLElement对象被用作上下文,那么 context 属性将返回那个HTMLElement。如果没有使用上下文或者使用了多个元素(如我在本章前面使用的例子),那么上下文属性返回undefined。清单 5-13 展示了这个属性的使用。

清单 5-13 。确定 jQuery 对象的上下文

...
<script type="text/javascript">
    $(document).ready(function() {
        var jq1 = $("img:odd");
        console.log("No context: " +jq1.context.tagName);

        var jq2 = $("img:odd", $(".drow"));
        console.log("Multiple context elements: " +jq2.context.tagName);

        var jq3 = $("img:odd", document.getElementById("oblock"));
        console.log("Single context element: " +jq3.context.tagName);
    });
</script>
...

该脚本选择不使用上下文、多个上下文对象和单个上下文对象的元素。输出如下所示:

No context: undefined
Multiple context elements: undefined
Single context element: DIV

处理 DOM 对象

jQuery 并没有取代 DOM 这让工作变得容易多了。还在使用HTMLElement对象(我在第二章中介绍过),jQuery 库使得在jQuery对象和 DOM 对象之间切换变得很容易。您可以轻松地从传统的 DOM 迁移到 jQuery,这是 jQuery 优雅的一部分,有助于保持与非 jQuery 脚本和库的兼容性。

从 DOM 对象创建 jQuery 对象

您可以通过将一个HTMLElement对象或一组HTMLElement对象作为参数传递给$函数来创建jQuery对象。这在处理不是用 jQuery 编写的 JavaScript 代码时,或者在 jQuery 公开底层 DOM 对象的情况下,比如事件处理,会很有用。清单 5-14 包含了一个例子。

清单 5-14 。从 DOM 对象创建 jQuery 对象

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

        var elems = document.getElementsByTagName("img");

        $(elems).mouseenter(function(e) {
           $(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           $(this).css("opacity", 1.0);
        })
    });
</script>
...

在这个例子中,我使用document.getElementsByTagName方法选择文档中的img元素,而不是直接使用带有选择器的 jQuery。我将这个方法的结果(它是一个HTMLElement对象的集合)传递给$函数,该函数返回一个常规的jQuery对象,我可以像前面的例子一样使用它。

这个脚本还演示了如何从单个HTMLElement对象创建一个 jQuery 对象:

...
$(this).css("opacity", 1.0);
...

当您处理事件时,jQuery 将变量this的值设置为正在处理事件的HTMLElement。我在第九章中描述了 jQuery 事件支持,所以我不想在本章深入讨论这个主题(尽管我在本章稍后会再次提到包含这些语句的函数)。

将 jQuery 对象视为数组

您可以将一个jQuery对象视为一组HTMLElement对象。这意味着您可以使用 jQuery 提供的高级特性,但仍然可以直接访问 DOM。您可以使用length属性或size方法来确定在jQuery对象中收集了多少元素,并通过使用数组样式的索引(使用[]括号)来访问单个 DOM 对象。

image 提示你可以使用toArray方法从jQuery对象中提取HTMLElement对象作为数组。我喜欢使用jQuery对象本身,但有时使用 DOM 对象也很有用,比如在处理不是用 jQuery 编写的遗留代码时。

清单 5-15 展示了如何枚举一个 jQuery 对象的内容来访问其中包含的HTMLElement对象。

清单 5-15 。将 jQuery 对象视为数组

...
<script type="text/javascript">
    $(document).ready(function() {
        var elems = $("img:odd");
        for (var i = 0; i < elems.length; i++) {
            console.log("Element: " + elems[i].tagName + " " + elems[i].src);
        }
    });
</script>
...

在清单中,我使用$函数选择奇数编号的img元素,并枚举所选元素以将tagNamesrc属性的值打印到控制台。结果如下:

Element: IMGhttp://www.jacquisflowershop.com/jquery/daffodil.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/peony.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/snowdrop.png

在 DOM 对象上迭代一个函数

each方法允许您为 jQuery 对象中的每个 DOM 对象定义一个函数。清单 5-16 给出了一个演示。

清单 5-16 。使用 each 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img:odd").each(function(index, elem) {
            console.log("Element: " + elem.tagName + " " + elem.src);
        });
    });
</script>
...

jQuery 向指定的函数传递两个参数。第一个是集合中元素的索引,第二个是元素对象本身。在本例中,我将标记名和src属性的值写入控制台,产生与前面脚本相同的结果:

Element: IMGhttp://www.jacquisflowershop.com/jquery/daffodil.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/peony.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/snowdrop.png

查找索引和特定元素

index方法允许您在 jQuery 对象中查找HTMLElement的索引。您可以使用HTMLElementjQuery对象作为参数来传递您想要的索引。当您使用一个jQuery对象时,第一个匹配的元素是其索引被返回的元素。清单 5-17 给出了一个演示。

清单 5-17 。定位 HTMLElement 的索引

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

        var elems = $("body *");

        // find an index using the basic DOM API
        var index = elems.index(document.getElementById("oblock"));
        console.log("Index using DOM element is: " + index);

        // find an index using another jQuery object
        index = elems.index($("#oblock"));
        console.log("Index using jQuery object is: " + index);
    });
</script>
...

在这个例子中,我使用 DOM API 的getElementById方法找到一个方法,通过id属性值找到一个div元素。这将返回一个HTMLElement对象。然后我在一个jQuery对象上使用index方法来查找代表div元素的对象的索引。我使用一个通过$函数获得的jQuery对象重复这个过程。我将两种方法的结果写入控制台,控制台产生以下结果:

Index using DOM element is: 2
Index using jQuery object is: 2

您也可以将一个string传递给index方法。当你这样做的时候,string被解释为一个选择器。然而,这种方法导致index方法的行为方式与前面的例子不同。清单 5-18 提供了一个演示。

清单 5-18 。使用索引方法的选择器版本

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

        var imgElems = $("img:odd");
        // find an index using a selector
        index = imgElems.index("body *");
        console.log("Index using selector is: " + index);

        // perform the same task using a jQuery object
        index = $("body *").index(imgElems);
        console.log("Index using jQuery object is: " + index);

    });
</script>
...

当您将一个string传递给index方法时,元素集合的使用顺序会改变。jQuery 使用选择器匹配元素,然后返回 jQuery 对象中第一个元素的匹配元素的索引,在该 jQuery 对象上调用了 index 方法。这意味着这种说法:

...
index = imgElems.index("body *");
...

相当于这个语句:

...
index = $("body *").index(imgElems);
...

本质上,传递字符串参数颠倒了两组元素的考虑方式。该列表产生以下结果:

Index using selector is: 10
Index using jQuery object is: 10

image 提示我们可以使用没有参数的index方法来获得一个元素相对于它的兄弟元素的位置。这在使用 jQuery 探索 DOM 时会很有用,这是《??》第七章的主题。

get方法是对index方法的补充,这样您可以指定一个索引并在 jQuery 对象中的该位置接收HTMLElement对象。这与我在本章前面描述的使用数组风格的索引有相同的效果。清单 5-19 提供了一个演示。

清单 5-19 。获取给定索引处的 HTMLElement 对象

...
<script type="text/javascript">
    $(document).ready(function() {
        var elem = $("img:odd").get(1);
        console.log("Element: " + elem.tagName + " " + elem.src);
    });
</script>
...

在这个脚本中,我选择奇数编号的img元素,使用get方法检索索引 1 处的HTMLElement对象,并将tagNamesrc属性的值写入控制台。该脚本的输出如下:

Element: IMGhttp://www.jacquisflowershop.com/jquery/peony.png

修改多个元素和链接方法调用

jQuery 如此简洁和富有表现力的一个特性是,调用一个jQuery对象上的方法通常会修改该对象包含的所有元素。我说通常是,因为有些方法执行不适用于多个元素的操作,你会在后面的章节看到这样的例子。清单 5-20 展示了如何使用 DOM API 对多个元素执行操作。

清单 5-20 。使用 DOM API 对多个元素进行操作

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

        var labelElems = document.getElementsByTagName("label");
        for (var i = 0; i < labelElems.length; i++) {
            labelElems[i].style.color = "blue";
        }
    });
</script>
...

script元素中的语句选择所有的label元素,并将 CSS color属性的值设置为blue。清单 5-21 展示了如何使用 jQuery 执行同样的任务。

清单 5-21 。使用 jQuery 对多个元素进行操作

...
<script type="text/javascript">
    $(document).ready(function () {
        $("label").css("color", "blue");
    });
</script>
...

我可以使用一条 jQuery 语句来执行这项任务,这比使用 DOM API 要简单得多——我承认这不是一个很大的区别,但是它可以安装在一个复杂的 web 应用中。jQuery 语句也更容易阅读和理解,这有助于 JavaScript 代码的长期维护。

对象的另一个很好的特性是它实现了一个流畅的 API ??。这意味着无论何时调用修改对象内容的方法,该方法的结果都是另一个 jQuery 对象。这看起来很简单,但是它允许你执行方法链接,如清单 5-22 所示。

清单 5-22 。jQuery 对象上的方法链接方法调用

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

        $("label").css("color", "blue").css("font-size", ".75em");

        var labelElems = document.getElementsByTagName("label");
        for (var i = 0; i < labelElems.length; i++) {
            labelElems[i].style.color = "blue";
            labelElems[i].style.fontSize = ".75em";
        }
    });
</script>
...

在这个例子中,我使用$函数创建一个 jQuery 对象,调用css方法为color属性设置一个值,然后再次调用css方法,这次是为了设置font-size属性。我还展示了使用基本 DOM API 的等效添加。您可以看到,要达到同样的效果并不需要太多的工作,因为您已经有了一个for循环来枚举所选的元素。

当链接方法对包含在jQuery对象中的元素集进行更大的改变时,您开始从 fluent API 中获得真正的好处。清单 5-23 提供了一个演示。

清单 5-23 。一个更复杂的链接示例

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

        $("label").css("color", "blue").add("input[name!='rose']")
                    .filter("[for!='snowdrop']").css("font-size", ".75em");

        var elems = document.getElementsByTagName("label");
        for (var i = 0; i < elems.length; i++) {
            elems[i].style.color = "blue";
            if (elems[i].getAttribute("for") != "snowdrop") {
                elems[i].style.fontSize= ".75em";
            }
        }
        elems = document.getElementsByTagName("input");
        for (var i = 0; i < elems.length; i++) {
            if (elems[i].getAttribute("name") != "rose") {
                elems[i].style.fontSize= ".75em";
            }
        }
    });
</script>
...

这是一个夸张的例子,但是它展示了 jQuery 提供的灵活性。让我们分解链接的方法,以了解正在发生的事情。我从这个开始:

...
$("label").css("color", "blue")
...

我已经选择了文档中所有的label元素,并将它们的 CSS color属性的值设置为blue。下一步如下:

...
$("label").css("color", "blue").add("input[name!='rose']")
...

add方法将匹配指定选择器的元素添加到jQuery对象中。在本例中,我选择了没有值为rosename属性的input元素。这些元素与之前匹配的元素组合在一起,给我一个包含混合了labelinput元素的jQuery对象。你会在第六章的中看到更多的add方法。下面是下一个新增内容:

...
$("label").css("color", "blue").add("input[name!='rose']").filter("[for!='snowdrop']")
...

filter方法删除 jQuery 对象中不满足指定条件的所有元素。我在第六章的中更深入地解释了这个方法,但是现在知道它允许我从 jQuery 对象中移除任何具有值为snowdropfor属性的元素就足够了。

...
$("label").css("color", "blue").add("input[name!='rose']")
    .filter("[for!='snowdrop']").css("font-size", ".75em");
...

最后一步是再次调用css方法,这次将font-size属性设置为.75em。最终结果如下:

  1. 所有的label元素都被赋予了color CSS 属性的值blue
  2. 除了具有snowdrop属性值的标签元素之外,所有标签元素都被赋予 CSS font-size属性的值.75em
  3. 所有没有rosename属性值的input元素都被赋予 CSS font-size属性的.75em值。

使用基本的 DOM API 实现相同的效果要复杂得多,我在编写这个脚本时遇到了一些困难。例如,我认为我可以使用第二章中描述的document.querySelectorAll方法,使用选择器input[name!='rose']选择input元素,但是结果证明这种属性过滤器不能与该方法一起工作。然后,我试图通过将两个getElementsByTagName调用的结果连接在一起来避免重复调用来设置font-size值,但是这本身就是一个痛苦的经历。我不想赘述这一点,特别是因为您必须已经对 jQuery 有了一定的了解才能阅读这本书,但是 jQuery 提供了一定程度的流畅性和表现力,这是使用基本的 DOM API 不可能实现的。

处理事件

回到我开始这一章的脚本,你可以看到我将两个方法调用链接在一起,如清单 5-24 中突出显示的。

清单 5-24 。示例脚本中的链式方法调用

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img:odd").mouseenter(function(e) {
           $(this).css("opacity", 0.5);
        }).mouseout(function(e) {
           $(this).css("opacity", 1.0);
        })
    });
</script>
...

我链接的方法是mouseentermouseout。这些方法让我为我在第二章的中描述的mouseentermouseout事件定义处理函数。我在第九章中介绍了 jQuery 对事件的支持,但我只是想展示如何使用jQuery对象的行为来为你选择的所有元素指定一个单独的处理方法。

摘要

在本章中,我向您介绍了您的第一个 jQuery 脚本,并使用它来演示 jQuery 库的一些关键特性:$函数、ready事件、jQuery结果对象,以及 jQuery 如何补充而不是取代作为 HTML 规范一部分的内置 DOM API。

六、管理元素选择

大多数时候,jQuery 的使用遵循一个独特的两步模式。第一步是选择一些元素,第二步是对这些元素执行一个或多个操作。在这一章中,我将重点介绍第一步,向您展示如何控制 jQuery 选择,并根据您的具体需求对其进行裁剪。我还将向您展示如何使用 jQuery 导航 DOM。在这两种情况下,您都从一个选择开始并对其执行操作,直到它只包含您需要的元素。正如您将看到的,开始时的元素和结束时的元素之间的关联可以简单,也可以复杂。表 6-1 对本章进行了总结。

表 6-1 。章节总结

问题 解决办法 列表
展开选择。 使用add方法。 one
将选择范围缩小到单个元素。 使用firstlasteq方法。 Two
将选择范围缩小到一系列元素。 使用slice方法。 three
通过应用过滤器减少选择。 使用filternot方法。 4, 5
根据选定元素的后代缩小选择范围。 使用has方法。 six
从现有选择中投影新选择。 使用map方法。 seven
检查是否至少有一个选定元素满足特定条件。 使用is方法。 eight
恢复到以前的选择。 使用end方法。 nine
将上一个选择添加到当前选择。 使用addBack方法。 Ten
导航到选定元素的子代和后代。 使用childrenfind方法。 11–13
导航到所选元素的父元素。 使用parent方法。 Fourteen
导航到祖先或选定的元素。 使用parents方法。 Fifteen
导航到元素的祖先,直到遇到特定的元素。 使用parentsUntil方法。 16, 17
导航到与选择器匹配或者是特定元素的最近祖先。 使用closest方法。 18, 19
导航到最近的定位祖先。 使用offsetParent方法。 Twenty
导航到所选元素的同级元素。 使用siblings方法。 21, 22
导航到所选元素的上一个或下一个同级元素。 使用下一个、prevnextAllprevAllnextUntilprevUntil方法。 Twenty-three

自上一版以来,JQUERY 发生了变化

在 jQuery 1.9 中,addBack方法已经用addBack方法取代了 jQuery 1.8 的addSelf方法。这些方法执行相同的功能,这将在本章的“更改然后展开选择”一节中演示。

扩展选择

add方法允许您通过添加额外的元素来扩展jQuery对象的内容。表 6-2 显示了你可以使用的不同参数。

表 6-2 。添加方法参数类型

争论 描述
add(selector) add(selector, context) 添加选择器匹配的所有元素,无论有无上下文。
add(HTMLElement) add(HTMLElement[]) 添加单个HTMLElement或一组HTMLElement对象。
add(jQuery) 添加指定jQuery对象的内容。

像许多 jQuery 方法一样,add方法返回一个jQuery对象,在这个对象上可以调用其他方法,包括对add方法的进一步调用。清单 6-1 展示了使用add方法来扩展一组元素。

清单 6-1 。使用 add 方法

<!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"/>
    <script type="text/javascript">
        $(document).ready(function () {

            var labelElems = document.getElementsByTagName("label");
            var jq = $("img[src*=daffodil]");

            $("img:even").add("img[src*=primula]").add(jq)
                .add(labelElems).css("border", "thick double red");

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

image 注意一个常见的错误是假设remove方法是add方法的对应方法,这会缩小选择范围。事实上,remove方法改变了 DOM 的结构,正如我在第七章中解释的那样。请使用我在“缩小选择范围”一节中描述的方法之一。

本例中的脚本使用所有三种方法向初始选择添加元素:使用另一个选择器、一些HTMLElement对象和另一个jQuery对象。一旦我建立了我的对象集,我就调用css方法来为border属性设置一个值,其效果是在选中的元素周围画一个红色的粗边框,如图图 6-1 所示。

9781430263883_Fig06-01.jpg

图 6-1 。使用 add 方法扩展选定内容

缩小选择范围

有很多方法可以让你从选择中移除元素,如表 6-3 所述。在每种情况下,这些方法都返回一个新的jQuery对象,该对象包含减少的元素选择 。调用该方法的jQuery对象保持不变。

表 6-3 。过滤元素的方法

方法 描述
eq(index) 移除除指定索引处的元素之外的所有元素。
filter(condition) 移除不符合指定条件的元素。请参阅后面的讨论,了解可以在此方法中使用的参数的详细信息。
first() 移除除第一个元素之外的所有元素。
has(selector)``has(jQuery)``has(HTMLElement) 删除没有与指定选择器或jQuery对象匹配的后代的元素,或者其后代不包含指定HTMLElement对象的元素。
last() 移除除最后一个元素之外的所有元素。
not(condition) 移除所有符合条件的元素。有关如何指定条件的详细信息,请参见后面的讨论。
slice(start, end) 移除指定索引值范围之外的所有元素。

将选择缩小到特定元素

三种最基本的缩小方法是firstlasteq。这三种方法允许您根据其在jQuery对象中的位置选择特定的元素。清单 6-2 提供了一个演示。

清单 6-2 。根据元素位置减少选择

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

        var jq = $("label");

        jq.first().css("border", "thick double red");
        jq.last().css("border", "thick double green");
        jq.eq(2).css("border", "thick double black");
        jq.eq(-2).css("border", "thick double black");

    });
</script>
...

注意,我调用了两次eq方法。当该方法的参数为正时,索引从jQuery对象中的第一个元素开始计数。当参数为负时,从最后一个元素开始向后计数。你可以在图 6-2 中看到这个脚本的效果。

9781430263883_Fig06-02.jpg

图 6-2 。将选择范围缩小到特定元素

按范围缩小选择范围

slice方法将选择缩小到一系列元素,如清单 6-3 所示。

清单 6-3 。使用切片方法

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

        var jq = $("label");

        jq.slice(0, 2).css("border", "thick double black");
        jq.slice(4).css("border", "thick solid red");

    });
</script>
...

slice方法的参数是开始选择的索引和结束选择的索引。索引是从零开始的,所以我在示例中使用的参数(02)选择了前两个元素。如果省略第二个参数,则选择继续到元素集的末尾。通过为一组六个元素指定一个参数4,我选择了最后两个元素(它们的索引值为45)。你可以在图 6-3 中看到这个脚本的结果。

9781430263883_Fig06-03.jpg

图 6-3 。按范围缩小选择范围

过滤元件

filter方法从选择中删除不满足指定条件的元素。表 6-4 显示了可用于表示过滤条件的不同参数。

表 6-4 。筛选方法参数类型

争论 描述
filter(selector) 移除与选择器不匹配的元素。
filter(HTMLElement) 移除指定元素以外的所有元素。
filter(jQuery) 删除不包含在指定的jQuery对象中的元素。
filter(function(index)) 为每个元素调用该函数;函数返回false的那些被删除。

清单 6-4 展示了指定过滤器的所有四种方式。

清单 6-4 。指定过滤器

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img").filter("[src*=s]").css("border", "thick double red");

        var jq = $("[for*=p]" );
        $("label").filter(jq).css("color", "blue");

        var elem = document.getElementsByTagName("label")[1];
        $("label").filter(elem).css("font-size", "1.5em");

        $("img").filter(function(index) {
            return this.getAttribute("src") == "peony.png" || index == 4;
        }).css("border", "thick solid red")
    });
</script>
...

前三种技术是不言而喻的:您可以基于选择器、另一个jQuery对象或一个HTMLElement对象进行过滤。

第四种技术依赖于一个function,需要更多的解释。jQuery 为jQuery对象包含的每个元素调用一次该函数。如果函数返回true,那么元素被保留,如果函数返回false,它被移除。

有一个参数传递给该函数,它是调用该函数的元素的索引。另外,将this变量设置为需要处理的 HTMLElement对象。在清单中,如果元素的 src属性和特定的索引值有特定的值,我将返回true。你可以在图 6-4 中看到结果。

9781430263883_Fig06-04.jpg

图 6-4 。使用过滤方法

image 提示你可能想知道为什么我在过滤函数中对HTMLElement使用了getAttribute方法,而不是调用src属性。原因是getAttribute方法将返回我为文档中的src 属性设置的值(这是一个相对 URL),但是src 属性将返回一个完全限定的 URL。对于这个例子,相对 URL 更容易处理。

filter方法的补充是not,它的工作方式基本相同,但过滤过程相反。表 6-5 显示了使用not方法应用条件的不同方式。

表 6-5 。非方法参数类型

争论 描述
not(selector) 移除与选择器匹配的元素。
not(HTMLElement[]) not(HTMLElement) 移除一个或多个指定元素。
not(jQuery) 移除包含在指定jQuery对象中的元素。
not(function(index)) 为每个元素调用该函数;函数返回true的那些被删除。

基于前面的例子,清单 6-5 展示了not方法的使用。

清单 6-5 。使用 not 方法

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

        $("img").not("[src*=s]").css("border", "thick double red");

        var jq = $("[for*=p]");
        $("label").not(jq).css("color", "blue");

        var elem = document.getElementsByTagName("label")[1];
        $("label").not(elem).css("font-size", "1.5em");

        $("img").not(function(index) {
            return this.getAttribute("src") == "peony.png" || index == 4;
        }).css("border", "thick solid red")
    });
</script>
...

你可以在图 6-5 中看到这个脚本的效果。当然,这与前一个例子的效果相反。

9781430263883_Fig06-05.jpg

图 6-5 。使用 not 方法过滤元素

减少基于后代的选择

has方法将选择减少到具有特定后代的元素,通过选择器或一个或多个HTMLElement对象指定,如清单 6-6 所示。

清单 6-6 。使用散列方法

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

        $("div.dcell").has("img[src*=aster]").css("border", "thick solid red");
        var jq = $("[for*=p]");
        $("div.dcell").has(jq).css("border", "thick solid blue");

    });
</script>
...

在第一种情况下,我使用了一个选择器,将选择范围缩小到至少有一个后代元素img的元素,其src属性值包含aster。在第二种情况下,我使用了一个jQuery对象,我将选择范围缩小到至少有一个后代的元素,这个后代的for属性的值包含字母p。你可以在图 6-6 中看到这个脚本的效果。

9781430263883_Fig06-06.jpg

图 6-6 。使用 has 方法减少选择

映射选择

map方法提供了一种灵活的方式来使用一个jQuery对象作为基于函数创建另一个对象的手段。源jQuery对象中的每个元素都调用该函数,函数返回的HTMLElement对象包含在结果jQuery对象中,如清单 6-7 所示。

清单 6-7 。使用地图方法

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

        $("div.dcell").map(function(index, elem) {
            return elem.getElementsByTagName("img")[0];
        }).css("border", "thick solid red");

        $("div.dcell").map(function(index, elem) {
            return $(elem).children()[1];
        }).css("border", "thick solid blue");

    });
</script>
...

在这个脚本中,我执行了两个映射操作。第一个使用 DOM API 返回每个元素中包含的第一个img元素,第二个使用 jQuery 返回 children 方法返回的jQuery对象中的第一个项目(我将在本章后面详细解释这个方法,但是顾名思义,它返回一个jQuery对象中每个元素的子节点)。你可以在图 6-7 的中看到结果。

9781430263883_Fig06-07.jpg

图 6-7 。使用地图方法

image 提示每次调用函数只能返回一个元素。如果您想要为每个源元素投影多个结果元素,您可以组合使用eachadd方法,我在第八章的中对此进行了描述。

测试选择

is方法确定jQuery对象中的一个或多个元素是否满足特定条件。表 6-6 显示了你可以传递给is方法的参数。

表 6-6 。是方法参数类型

争论 描述
is(selector) 如果jQuery对象包含选择器匹配的至少一个元素,则返回true
is(HTMLElement[]) is(HTMLElement) 如果jQuery对象包含指定的元素,或者指定数组中的至少一个元素,则返回true
is(jQuery) 如果jQuery对象包含 argument 对象中的至少一个元素,则返回true
is(function(index)) 如果函数至少返回true一次,则返回true

当您指定一个函数作为条件时,jQuery 将为jQuery对象中的每个元素调用一次该函数,将元素的索引作为函数参数传递,并将this变量设置为元素本身。清单 6-8 显示了正在使用的is方法。

image 注意这个方法返回一个布尔值。正如我在《??》第五章中提到的,并不是所有的 jQuery 方法都返回一个jQuery对象。

清单 6-8 。使用 is 方法

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

        var isResult = $("img").is(function(index){
            return this.getAttribute("src") == "rose.png";
        });
        console.log("Result: " + isResult);

    });
</script>
...

该脚本测试jQuery对象是否包含一个其src属性值为rose.png的元素,并将结果写到控制台,如下所示:

Result: true

更改然后展开选择

当您通过将方法链接在一起来修改选择时,jQuery 会保留一个历史堆栈,您可以使用几个方法来利用这一点,如表 6-7 中所述。

表 6-7 。展开选择堆栈的方法

方法 描述
end() 从堆栈中弹出当前选择并返回到上一个选择。
addBack() addBack(selector) 将上一个选择添加到当前选择,并使用可选选择器过滤上一个选择。

end方法返回到之前的选择,这允许你选择一些元素,扩大或缩小选择,执行一些操作,然后返回到最初的选择,如清单 6-9 所示。

清单 6-9 。使用结束方法

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

        $("label").first().css("border", "thick solid blue")
            .end().css("font-size", "1.5em");

    });
</script>
...

在这个脚本中,我首先选择文档中所有的 label元素。然后,我通过调用first方法缩小选择范围(以获得第一个匹配的元素),然后使用css方法为 CSS border属性设置一个值,其效果是只更改第一个选择的元素的 CSS 属性。

然后,我调用end方法返回到之前的选择(这将把选择的第一个label元素移回到label元素的所有)。然后我再次调用css方法,这一次为font-size属性设置一个值。这个 CSS 属性应用于所有的label元素,如图图 6-8 所示。

9781430263883_Fig06-08.jpg

图 6-8 。使用结束方法

addBack方法将堆栈中上一个选择的内容添加到当前选择中。清单 6-10 显示了正在使用的addBack方法。

清单 6-10 。使用 andSelf 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("div.dcell").children("img").addBack().css("border", "thick solid blue");
    });
</script>
...

image jQuery 1.9/2.0 中的addBack方法取代了andSelf方法。新方法执行相同的功能,并支持一个附加的选择器参数来过滤选择。

在这个例子中,我选择了所有属于dcell类成员的 div元素,然后使用 children 方法选择所有属于它们的子元素的img元素(我将在本章后面的导航 DOM 一节中详细解释children方法)。然后我调用addBack方法,该方法在一个jQuery对象中将之前的选择(div元素)与当前的选择(img元素)结合起来。最后,我调用使用css方法为选中的元素设置边框。你可以在图 6-9 中看到这个脚本的效果。

9781430263883_Fig06-09.jpg

图 6-9 。使用回加方法

在 DOM 中导航

您可以使用一个选择作为在 DOM 中导航的起点,使用一个选择作为创建另一个选择的起点。在接下来的小节中,我将描述和演示 jQuery 导航方法。在本节中,我提到了我在第二章中介绍的元素之间可能存在的不同类型的关系。

image 提示下面几节描述的所有方法都返回一个jQuery对象。如果有匹配的元素,这个对象将包含它们,如果没有匹配的元素,这个对象将为空(也就是说,length属性将为零)。

向下导航到层次结构

当您向下导航 DOM 层次结构时,您选择的是包含在一个jQuery对象中的元素的子元素和子元素。表 6-8 描述了相关的 jQuery 方法。

表 6-8 。方法在 DOM 层次结构中向下导航

方法 描述
children() 选择jQuery对象中所有元素的子元素。
children(selector) 选择所有与选择器匹配的元素,这些元素是jQuery对象中元素的子元素。
contents() 返回jQuery对象中所有元素的子元素和文本内容。
find() 选择jQuery对象中元素的后代。
find(selector) 选择与选择器匹配的元素,这些元素是jQuery对象中元素的后代。
find(jQuery)``find(HTMLElement) 选择jQuery对象和 argument 对象中元素的子元素之间的交集。

children方法将只选择那些是jQuery对象中每个元素的直接后代的元素,可选地由选择器过滤。find方法将选择所有的后代元素,而不仅仅是直接的。contents方法将返回子元素,以及任何文本内容。清单 6-11 显示了正在使用的childrenfind方法。

清单 6-11 。使用子代和查找方法

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

        var childCount = $("div.drow").children().each(function(index, elem) {
            console.log("Child: " + elem.tagName + " " + elem.className);
        }).length;
        console.log("There are " + childCount + " children");

        var descCount = $("div.drow").find("img").each(function(index, elem) {
            console.log("Descendant: " + elem.tagName + " " + elem.src);
        }).length;
        console.log("There are " + descCount + " img descendants");

    });
</script>
...

在这个例子中,我使用不带选择器的children方法和带选择器的find方法。我将所选元素的详细信息以及选择了多少元素写入控制台。该脚本的控制台输出如下:

Child: DIV dcell
Child: DIV dcell
Child: DIV dcell
Child: DIV dcell
Child: DIV dcell
Child: DIV dcell
There are 6 children
Descendant: IMGhttp://www.jacquisflowershop.com/jquery/aster.png
Descendant: IMGhttp://www.jacquisflowershop.com/jquery/daffodil.png
Descendant: IMGhttp://www.jacquisflowershop.com/jquery/rose.png
Descendant: IMGhttp://www.jacquisflowershop.com/jquery/peony.png
Descendant: IMGhttp://www.jacquisflowershop.com/jquery/primula.png
Descendant: IMGhttp://www.jacquisflowershop.com/jquery/snowdrop.png
There are 6 img descendants

childrenfind方法的一个很好的特性是在选择中不会收到重复的元素。清单 6-12 提供了一个演示。

清单 6-12 。生成具有重叠后代的选择

...
<script type="text/javascript">
    $(document).ready(function() {
        $("div.drow").add("div.dcell").find("img").each(function(index, elem) {
            console.log("Element: " + elem.tagName + " " + elem.src);
        });
    });
</script>
...

在这个例子中,我首先创建一个包含所有属于drow类的div元素和所有属于dcell类的div元素的jQuery对象。请注意,dcell类的所有成员都包含在drow类的成员中,这意味着当我使用带有img选择器的find方法时,您会有重叠的后代集和潜在的重复,因为img元素是两个div元素类的后代。但是 jQuery 确保返回的元素中没有重复的元素,如清单生成的控制台输出所示:

Element: IMGhttp://www.jacquisflowershop.com/jquery/aster.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/daffodil.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/rose.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/peony.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/primula.png
Element: IMGhttp://www.jacquisflowershop.com/jquery/snowdrop.png

使用 find 方法创建交集

你可以将一个jQuery对象、一个HTMLElement对象或者一组HTMLElement对象作为参数传递给find方法。当你这样做时,你选择了源jQuery对象的后代和参数对象的元素之间的交集。清单 6-13 提供了一个演示。

清单 6-13 。使用 find 方法创建交集

...
<script type="text/javascript">
    $(document).ready(function() {
        var jq = $("label").filter("[for*=p]").not("[for=peony]");
        $("div.drow").find(jq).css("border", "thick solid blue");
    });
</script>
...

正如这个脚本所演示的,这种方法的优点是您可以指定与后代相交的元素。我创建了一个jQuery对象,然后使用filternot方法对其进行缩减。然后,这个对象成为另一个包含drow类中所有div元素的jQuery对象上的find方法的参数。最后的选择是div.drow元素的后代和我的label元素精简集之间的交集。你可以在图 6-10 中看到脚本的效果。

9781430263883_Fig06-10.jpg

图 6-10 。使用 find 方法创建交集

在层级中向上导航

当您在 DOM 层次结构中导航时,您会对包含在一个jQuery对象中的元素的父元素和祖先元素感兴趣。表 6-9 显示了你可以用来向上导航的方法。

表 6-9 。在 DOM 层次结构中向上导航的方法

方法 描述
closest(selector) closest(selector, context) 为与指定选择器相交的jQuery对象中的每个元素选择最近的祖先。
closest(jQuery) closest(HTMLElement) 为与 argument 对象中包含的元素相交的jQuery对象中的每个元素选择最近的祖先。
offsetParent() 查找 CSS 位置属性值为fixedabsoluterelative的最近的祖先。
parent() parent(selector) 选择jQuery对象中每个元素的父元素,可选地由选择器过滤。
parents() parents(selector) 选择jQuery对象中每个元素的祖先,可选地由选择器过滤。
parentsUntil(selector) parentsUntil(selector, selector) 选择jQuery对象中每个元素的祖先,直到遇到匹配的选择器。可以使用第二个选择器过滤结果。
parentsUntil(HTMLElement)``parentsUntil(HTMLElement, selector)``parentsUntil(HTMLElement[]) 选择jQuery对象中每个元素的祖先,直到遇到一个指定的元素。可以使用选择器过滤结果。

选择父元素

parent方法为jQuery对象中的每个元素选择父元素。如果您提供了一个选择器,那么只有匹配该选择器的父元素才会包含在结果中。清单 6-14 显示了使用中的父元素。

清单 6-14 。使用父元素

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

        $("div.dcell").parent().each(function(index, elem) {
            console.log("Element: " + elem.tagName + " " + elem.id);
        });

        $("div.dcell").parent("#row1").each(function(index, elem) {
            console.log("Filtered Element: " + elem.tagName + " " + elem.id);
        });

    });
</script>
...

在这个脚本中,我选择了所有属于dcell类的div元素,并调用parent方法来选择父元素。我还演示了如何使用带有选择器的parent方法。我使用each方法将关于所选父元素的信息写入控制台,控制台产生以下输出:

Element: DIV row1
Element: DIV row2
Filtered Element: DIV row1

选择祖先

parents方法(注意最后一个字母s)选择jQuery对象中元素的所有祖先,而不仅仅是直接的父元素。同样,您可以将选择器作为方法传递给参数来过滤结果。清单 6-15 演示了parents方法。

清单 6-15 。使用双亲方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img[src*=peony], img[src*=rose]").parents().each(function(index, elem) {
            console.log("Element: " + elem.tagName + " " + elem.className + " "
                             + elem.id);
        });
    });
</script>
...

在这个例子中,我选择了两个img元素,并使用了parents方法来选择它们的祖先。然后,我将每个祖先的信息写入控制台,产生以下输出:

Element: DIV dcell
Element: DIV drow row2
Element: DIV dcell
Element: DIV drow row1
Element: DIV dtable
Element: DIV  oblock
Element: FORM
Element: BODY
Element: HTML

选择祖先的一个变体由parentsUntil方法表示。对于jQuery对象中的每个元素,parentsUntil方法在 DOM 层次结构中一路向上,选择祖先元素,直到遇到匹配选择器的元素。清单 6-16 提供了一个演示。

清单 6-16 。使用 parentsUntil 方法

...
<script>
    $(document).ready(function() {
        $("img[src*=peony], img[src*=rose]").parentsUntil("form")
            .each(function(index, elem) {
                console.log("Element: " + elem.tagName + " " + elem.className
                        + " " + elem.id);
        });
    });
</script>
...

在这个例子中,选择每个元素的祖先,直到遇到一个form元素。该脚本的输出如下:

Element: DIV dcell
Element: DIV drow row2
Element: DIV dcell
Element: DIV drow row1
Element: DIV dtable
Element: DIV  oblock

请注意,匹配选择器的元素被排除在所选祖先之外。在本例中,这意味着表单元素被排除。您可以通过提供第二个选择器参数来过滤祖先集,如清单 6-17 所示。

清单 6-17 。筛选由 parentsUntil 方法选择的元素集

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

        $("img[src*=peony], img[src*=rose]").parentsUntil("form", ":not(.dcell)")
            .each(function(index, elem) {
            console.log("Element: " + elem.tagName + " " + elem.className
                        + " " + elem.id);
        });

    });
</script>
...

在这个例子中,我添加了一个选择器,它将过滤掉属于dcell类的元素。该脚本的输出如下:

Element: DIV drow row2
Element: DIV drow row1
Element: DIV dtable
Element: DIV  oblock

选择第一个匹配的祖先

closest方法为jQuery对象中的每个元素选择一个选择器匹配的第一个祖先。清单 6-18 提供了一个演示。

清单 6-18 。使用最接近的方法

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

        $("img").closest(".drow").each(function(index, elem) {
            console.log("Element: " + elem.tagName + " " + elem.className
                        + " " + elem.id);
        });

        var contextElem = document.getElementById("row1");
        $("img").closest(".drow", contextElem).each(function(index, elem) {
            console.log("Context Element: " + elem.tagName + " " + elem.className
                        + " " + elem.id);
        });

    });
</script>
...

在这个例子中,我选择文档中的img元素,然后使用closest方法找到属于drow类的最近的祖先。您可以通过指定一个HTMLElement对象作为该方法的第二个参数来缩小选择祖先的范围。不是上下文对象或者不是上下文对象后代的祖先被排除在选择之外。该脚本的输出如下:

Element: DIV drow row1
Element: DIV drow row2
Context Element: DIV drow row2

当您指定一个jQuery对象或一个或多个HTMLElement对象作为closest方法的参数时,jQuery 会沿着源jQuery对象中每个元素的层次结构向上搜索,匹配它找到的第一个参数对象。清单 6-19 中的展示了这一点。

清单 6-19 。使用一组参考对象的最近方法

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

        var jq = $("#row1, #row2, form");

        $("img[src*=rose]").closest(jq).each(function(index, elem) {
            console.log("Context Element: " + elem.tagName + " " + elem.className
                        + " " + elem.id);
        });

    });
</script>
...

在这个例子中,我选择文档中的一个img元素,然后使用closest方法选择祖先元素。我提供了一个包含form元素和带有row1row2 ID 的元素的jQuery对象作为closest方法的参数。jQuery 将选择与img元素最接近的元素。换句话说,它将开始在层次结构中向上移动,直到遇到 argument 对象中的一个元素。该脚本的输出如下:

Context Element: DIV drow row1

offsetParent是最近的主题的变体,并且资助第一个祖先,该祖先具有relativeabsolutefixedposition CSS 属性的值。这样的元素被称为定位祖先,在处理动画时找到一个元素会很有用(关于 jQuery 对动画支持的详细信息,请参见第十章)。清单 6-20 包含了这种方法的演示。

清单 6-20 。使用 offsetParent 方法

<!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">
        #oblock {position: fixed; top: 120px; left: 50px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("img[src*=aster]").offsetParent().css("background-color", "lightgrey");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

在这个示例文档的简化版本中,我使用 CSS 为具有oblockid的元素的position属性设置了一个值。在脚本中,我使用 jQuery 来选择一个img元素,然后调用offsetParent方法来查找位置最近的元素。该方法在层次结构中一直向上,直到到达具有所需值之一的元素。我使用css属性为所选元素的background-color属性设置一个值,如图 6-11 中的所示。

9781430263883_Fig06-11.jpg

图 6-11 。寻找位置最近的祖先

在层次结构中导航

DOM 导航的最终形式处理兄弟节点。jQuery 为此提供的方法在表 6-10 中描述。

表 6-10 。方法在 DOM 层次结构中导航

方法 描述
next() next(selector) jQuery对象中的每个元素选择紧邻的下一个兄弟元素,可选地由选择器过滤。
nextAll() nextAll(selector) jQuery对象中的每个元素选择所有下一个兄弟元素,可选地由选择器过滤。
nextUntil((selector) nextUntil(selector, selector) nextUntil(jQuery) nextUntil(jQuery, selector) nextUntil(HTMLElement[]) 为每个元素选择下一个兄弟元素,直到(不包括)与选择器匹配的元素或jQuery对象或HTMLElement数组中的元素。结果可以选择性地由选择器筛选,作为方法的第二个参数。
prev() prev(selector) jQuery对象中的每个元素选择上一个同级元素,可选地由选择器过滤。
prevAll() prevAll(selector) jQuery对象中的每个元素选择所有以前的兄弟元素,可选地由选择器过滤。
prevUntil(selector) prevUntil(selector, selector) prevUntil(jQuery) prevUntil(jQuery, selector) prevUntil(HTMLElement[]) 选择每个元素的前一个兄弟元素,直到(不包括)与选择器匹配的元素或jQuery对象或HTMLElement数组中的元素。结果可以选择性地由选择器筛选,作为方法的第二个参数。
siblings() siblings(selector) jQuery对象中的每个元素选择所有同级元素,可选地由选择器过滤。

选择所有兄弟姐妹

siblings方法选择一个jQuery对象中所有元素的所有兄弟元素。清单 6-21 展示了这种方法的使用。(对于这个清单,我已经返回了完整的花店文档)。

清单 6-21 。使用兄弟姐妹方法

<!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"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $("img[src*=aster], img[src*=primula]")
                .parent().siblings().css("border", "thick solid blue");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

在这个例子中,我选择了两个img元素,调用parent方法选择它们的父元素,然后调用siblings方法选择它们的兄弟元素。前一个和下一个兄弟都将被选中,我使用css方法为border属性设置一个值。在图 6-12 中可以看到效果。(我用了parent的方法让 CSS 属性的效果更清晰。)

9781430263883_Fig06-12.jpg

图 6-12 。选择同级元素

请注意,只选择了同级元素,而不是元素本身。当然,如果jQuery对象中的一个元素是另一个元素的兄弟元素,这种情况就会改变,如清单 6-22 所示。

清单 6-22 。重叠的兄弟集合

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#row1 div.dcell").siblings().css("border", "thick solid blue");
    });
</script>
...

在这个脚本中,我首先选择作为row1元素的子元素的所有div元素,然后调用siblings方法。选择中的每个元素都是至少一个其他元素的兄弟,正如你在图 6-13 中看到的。

9781430263883_Fig06-13.jpg

图 6-13 。重叠的同级元素

选择下一个和上一个兄弟

我不打算演示选择下一个和上一个兄弟的所有方法,因为它们的工作方式与其他导航方法相同。清单 6-23 显示了正在使用的nextAllprevAll方法。

清单 6-23 。使用 nextAll 和 prevAll 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img[src*=aster]").parent().nextAll().css("border", "thick solid blue");
        $("img[src*=primula]").parent().prevAll().css("border", "thick double red");
    });
</script>
...

该脚本为 aster 图像的父代选择下一个兄弟,为 primula 图像选择前一个兄弟。你可以在图 6-14 中看到这个脚本的效果。

9781430263883_Fig06-14.jpg

图 6-14 。选择下一个和上一个同级

摘要

在这一章中,我向您展示了如何控制 jQuery 选择并根据您的具体需求对其进行裁剪,包括添加元素、过滤元素、使用映射以及测试选择以评估条件。我还向您展示了如何使用 jQuery 选择作为导航 DOM 的起点,使用一个选择作为遍历文档以创建另一个选择的起点。在第七章中,我将向您展示如何使用选择来操作 DOM,应用 jQuery 方法来创建、移除、更改和创建 HTML 元素。

七、操纵 DOM

在前一章中,我向您展示了如何选择元素。您可以对选择做的最强大的事情之一是改变 HTML 文档本身的结构,这被称为操纵 DOM 。在这一章中,我将向你展示改变结构的不同方法,包括插入元素作为其他元素的子元素、父元素或兄弟元素。我还将向您展示如何创建新元素,如何将元素从文档的一部分移动到另一部分,以及如何完全删除元素。表 7-1 对本章进行了总结。

表 7-1 。章节总结

问题 解决办法 列表
创造新元素。 通过使用clone方法或使用 DOM API,将一个 HTML 片段传递给$函数。 1–3
将元素作为最后一个子元素插入。 使用append方法。 four
将元素作为第一个子元素插入。 使用prepend方法。 5, 6
在不同的位置插入相同的元素。 在插入元素之前克隆它们。 7, 8
插入一个jQuery对象的内容作为其他元素的子元素。 使用appendToprependTo方法。 nine
动态插入子元素。 将函数传递给appendprepend方法。 Ten
插入父元素。 使用wrap方法。 Eleven
向多个元素插入一个公共父元素。 使用wrapAll方法。 12, 13
包装元素的内容。 使用wrapInner方法。 Fourteen
动态包装元素。 将函数传递给wrapwrapInner方法。 Fifteen
插入同级元素。 使用afterbeforeinsertAfterinsertBefore方法。 16, 17
动态插入同级元素。 将函数传递给beforeafter方法。 Eighteen
用其他元素替换元素。 使用replaceWithreplaceAll方法。 Nineteen
动态替换元素。 将函数传递给replaceWith方法。 Twenty
从 DOM 中移除元素。 使用removedetach方法。 21–23
移除元素的内容。 使用empty方法。 Twenty-four
移除元素的父元素。 使用unwrap方法。 Twenty-five

自上一版以来,JQUERY 发生了变化

对于这一章,jQuery 1.9/2.0 中最重要的变化是在解释 HTML 字符串时采用了更严格的方法。然而,1.10/2.0.1 版本撤销了这一更改,并恢复了旧的 HTML 解析方法——详情请参见 HTML 解析的更改侧栏。

afterbeforereplaceWithappendToinsertBeforeinsertAfterreplaceAll方法的方式进行了一些幕后修改,以使它们处理jQuery对象的方式与其他 DOM 操作方法一致。这些变化不会影响本章中的技术。

创建新元素

当编写 web 应用时,您经常需要创建新元素并将它们插入到 DOM 中(尽管您也可以插入现有的元素,我将在本章后面解释)。在接下来的部分中,我将向您展示创建内容的不同方法。

image 提示理解创建新元素并不会自动将它们添加到 DOM 中是很重要的。您需要明确地告诉 jQuery 新元素应该放在文档中的什么位置,我将在本章后面解释这一点。

使用$函数创建元素

您可以通过向$函数传递一个 HTML 片段字符串来创建新元素。jQuery 解析字符串并创建相应的 DOM 对象。清单 7-1 包含了一个例子。

清单 7-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"/>
    <script type="text/javascript">
        $(document).ready(function() {

            var newElems = $("<div class='dcell'><img src='lily.png'/></div>");

            newElems.each(function (index, elem) {
                console.log("New element: " + elem.tagName + " " + elem.className);
            });

            newElems.children().each(function(index, elem) {
                console.log("Child: " + elem.tagName + " " + elem.src);
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required>
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required >
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required>
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required>
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required>
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

在这个例子中,我从一个 HTML 片段中创建了两个新元素:一个div元素和一个img元素。因为你处理的是 HTML,所以你可以使用包含结构的片段。在这种情况下,img元素是div元素的子元素。

对 HTML 解析的更改

当您将一个字符串传递给$函数时,jQuery 必须决定它是选择器还是 HTML 字符串。在 1.9 版本之前,如果字符串中的任何地方有标签,它就被认为是 HTML(关于标签的详细信息,参见第二章)。这带来了一些罕见的问题,复杂的选择器被解释为 HTML,因此在 jQuery 1.9/2.0 中改变了策略,只有以<字符开头的字符串才被认为是 HTML。

这被证明是一个不受欢迎的改变,因此在 jQuery 1.10/2.0.1 版本中又将该策略改了回来——但是警告说在检测 HTML 字符串的方式上可能会有进一步的改变。如果您正在处理可能不明确的字符串,您可以使用parseHTML方法,它将处理一个 HTML 字符串,而没有将其解释为选择器的风险。

$函数返回的jQuery对象只包含来自 HTML 片段的顶级元素。为了演示这一点,我使用了each函数将关于jQuery对象中每个元素的信息写入控制台。jQuery 不会丢弃子元素。它们可以通过我在第六章中描述的常用导航方法访问。为了演示这一点,我在jQuery对象上调用了children方法,并将每个子元素的信息打印到控制台。该脚本的输出如下:

New element: DIV dcell
Child: IMGhttp://www.jacquisflowershop.com/jquery/lily.png

image 提示您还可以提供一个 map 对象,指定应该应用于 HTML 元素的属性。你可以在第十二章中看到这个版本的$函数。

通过克隆现有元素创建新元素

您可以使用clone方法从现有元素创建新元素。这复制了一个jQuery对象中的所有元素,以及它们的所有后代。清单 7-2 给出了一个例子。

清单 7-2 。克隆元素

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

        var newElems = $("div.dcell").clone();

        newElems.each(function (index, elem) {
            console.log("New element: " + elem.tagName + " " + elem.className);
        });

        newElems.children("img").each(function(index, elem) {
            console.log("Child: " + elem.tagName + " " + elem.src);
        });

    });
</script>
...

在这个脚本中,我选择并克隆了所有属于dcell类的div元素。为了演示后代元素也被克隆,我使用了带有选择器的children方法来获得克隆的img元素。我已经将divimg元素的详细信息写入控制台,产生以下输出:

New element: DIV dcell
New element: DIV dcell
New element: DIV dcell
New element: DIV dcell
New element: DIV dcell
New element: DIV dcell
Child: IMGhttp://www.jacquisflowershop.com/jquery/aster.png
Child: IMGhttp://www.jacquisflowershop.com/jquery/daffodil.png
Child: IMGhttp://www.jacquisflowershop.com/jquery/rose.png
Child: IMGhttp://www.jacquisflowershop.com/jquery/peony.png
Child: IMGhttp://www.jacquisflowershop.com/jquery/primula.png
Child: IMGhttp://www.jacquisflowershop.com/jquery/snowdrop.png

image 提示您可以将值true作为参数传递给clone方法,以便在复制过程中包含事件处理程序和与元素相关的数据。省略此参数或指定值false会省略事件处理程序和数据。我将在第九章的中解释 jQuery 对事件的支持,并在第八章的中解释如何将数据与元素相关联。

使用 DOM API 创建元素

您可以直接使用 DOM API 来创建新的HTMLElement对象,当您使用其他技术时,这基本上就是 jQuery 在为您做的事情。我不打算解释 DOM API 的细节,但是清单 7-3 包含了一个简单的例子,让你知道如何使用这种技术。

清单 7-3 。使用 DOM API 创建新元素

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

        var divElem = document.createElement("div");
        divElem.classList.add("dcell");

        var imgElem = document.createElement("img");
        imgElem.src = "lily.png";

        divElem.appendChild(imgElem);

        var newElems = $(divElem);

        newElems.each(function (index, elem) {
            console.log("New element: " + elem.tagName + " " + elem.className);
        });

        newElems.children("img").each(function(index, elem) {
            console.log("Child: " + elem.tagName + " " + elem.src);
        });

    });
</script>
...

在这个例子中,我创建并配置了一个div HTMLElement和一个img HTMLElement,并将img指定为div的子节点,就像我在第一个例子中所做的一样。以这种方式创建元素没有错,但是因为这是一本关于 jQuery 的书,所以我不想过多地讨论 DOM API 而偏离主题。

我将div HTMLElement作为参数传递给 jQuery $函数,这样我就可以使用与其他示例相同的each函数。控制台输出如下:

New element: DIV dcell
Child: IMGhttp://www.jacquisflowershop.com/jquery/lily.png

插入子元素和后代元素

一旦我创建了元素,我就可以开始将它们插入到文档中。我从查看将一个元素插入另一个元素以创建子元素和后代元素的方法开始,如表 7-2 所述。

表 7-2 。插入子元素和后代元素的方法

方法 描述
append(HTML)``append(jQuery) 将指定元素作为 DOM 中所有元素的最后一个子元素插入。
prepend(HTML)``prepend(jQuery) 将指定元素作为 DOM 中所有元素的第一个子元素插入。
appendTo(jQuery) appendTo(HTMLElement[]) 将元素插入到jQuery对象中,作为参数指定的元素的最后一个子元素。
prependTo(HTML)``prependTo(jQuery) 将元素插入到jQuery对象中,作为参数指定的元素的第一个子元素。
append(function) prepend(function) 将一个函数的结果附加或预先添加到jQuery对象的元素中。

image 提示您还可以使用wrapInner方法插入子元素,我在“包装元素内容”一节中对此进行了描述。此方法在元素及其现有子元素之间插入一个新的子元素。另一个技巧是使用html方法,我在第八章的中描述过。

作为参数传递给这些方法的元素被作为子元素插入到jQuery对象中的每个元素的中,这使得使用我在第六章中向您展示的技术来管理选择变得尤为重要,以便它只包含您想要使用的元素。清单 7-4 给出了使用append方法的演示。

清单 7-4 。使用 append 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        var newElems = $("<div class='dcell'></div>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />");

        newElems.css("border", "thick solid red");

        $("#row1").append(newElems);
    });
</script>
...

我在这个脚本中以两种不同的方式使用了append方法:首先构建我的新元素集,然后将这些元素插入 HTML 文档。由于这是我描述的第一个 DOM 操作方法,我将花点时间演示一些行为,帮助您避免最常见的与 DOM 相关的 jQuery 错误。但首先,我们来看看剧本的效果。你可以在图 7-1 的中看到添加新元素的结果。

9781430263883_Fig07-01.jpg

图 7-1 。向文档中插入新元素

首先要看的是我使用append方法构建新元素的方式:

...
var newElems = $("<div class='dcell'/>").append("<img src='lily.png'/>")
    .append("<label for='lily'>Lily:</label>")
    .append("<input name='lily' value='0' required />");
...

我本可以创建一个包含所有元素的更大的 HTML 块,但是我想展示 DOM 操作方法的一个关键方面,即这些方法返回的jQuery对象包含与调用这些方法的对象相同的元素。

例如,我从一个包含一个div元素的jQuery对象开始,每个append方法的结果是一个包含相同div元素的jQuery对象,而不是我添加的元素。这意味着链接append调用会为最初选择的元素创建多个新的子元素。

要指出的下一个行为是,新创建的元素可能不会附加到文档中,但是您仍然可以使用 jQuery 来导航和修改它们。我想用边框突出显示新元素,所以我做了如下调用:

...
newElems.css("border", "thick solid red");
...

这是一个很好的特性,允许您创建和管理复杂的元素集,并在将它们添加到文档之前做好充分的准备。最后,我将新元素添加到文档中,如下所示:

...
$("#row1").append(newElems);
...

新元素将添加到选择的每个元素中。示例中的选择只有一个元素(带有row1中的id的元素),因此您有了添加到花店页面的新 lily 产品。

前置元素

append方法的补充是prepend,它将新的元素作为元素的第一个子元素插入到jQuery对象中。清单 7-5 包含了一个例子。

清单 7-5 。使用前置方法

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

        var orchidElems = $("<div class='dcell'/>")
            .append("<img src='orchid.png'/>")
            .append("<label for='orchid'>Orchid:</label>")
            .append("<input name='orchid' value='0' required />");

        var newElems = $("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />").add(orchidElems);

        newElems.css("border", "thick solid red");

        $("#row1, #row2").prepend(newElems);
    });
</script>
...

除了prepend方法之外,这个脚本还演示了另一个 jQuery DOM 操作特性:作为参数传递给这些方法之一的所有元素都被添加为jQuery对象中所有元素的子元素。在这个例子中,我创建了两个div元素,一个用于百合,一个用于兰花。我使用add方法将两组元素放在一个jQuery对象中。

image 提示add方法也接受包含 HTML 片段的字符串。您可以使用这个特性来替代使用jQuery对象构建新元素。

然后我创建另一个jQuery对象,它包含具有row1row2 id值的元素,并使用prepend方法将兰花和百合元素插入到文档中。你可以在图 7-2 中看到效果。新元素以红色边框突出显示。如图所示,lily 和兰花元素已经添加到两个 row 元素中。

9781430263883_Fig07-02.jpg

图 7-2 。向多个选定元素添加多个新元素

作为使用add方法的替代方法,您可以向 DOM 修改方法传递多个元素,如清单 7-6 所示。这个列表产生了与图 7-2 中所示相同的结果。

清单 7-6 。向 prepend 方法传递多个参数

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

        var orchidElems = $("<div class='dcell'/>")
            .append("<img src='orchid.png'/>")
            .append("<label for='orchid'>Orchid:</label>")
            .append("<input name='orchid' value='0' required />");

        var lilyElems = $("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />");

        orchidElems.css("border", "thick solid red");
        lilyElems.css("border", "thick solid red");

        $("#row1, #row2").prepend(lilyElems, orchidElems);
    });
</script>
...

image 提示我使用css方法在单独的语句中设置 CSS border属性,但这只是为了让示例更容易理解。事实上,我可以像任何其他 jQuery 方法一样链接对css方法的调用。

在不同的位置插入相同的元素

您只能向文档中添加一次新元素。此时,将它们用作 DOM 插入方法的参数会移动元素,而不是复制它们。清单 7-7 显示了这个问题。

清单 7-7 。向文档中添加新元素两次

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

        var orchidElems = $("<div class='dcell'/>")
            .append("<img src='orchid.png'/>")
            .append("<label for='orchid'>Orchid:</label>")
            .append("<input name='orchid' value='0' required />");

        var newElems = $("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />").add(orchidElems);

        newElems.css("border", "thick solid red");

        $("#row1").append(newElems);
        $("#row2").prepend(newElems);
    });
</script>
...

这个脚本的意图很清楚:将新元素追加到row1中,并将它们前置到row2中。当然,这不会发生,正如图 7-3 所示。

9781430263883_Fig07-03.jpg

图 7-3 。两次尝试向文档中添加新元素(但都失败了)

元素附加到row1上,但是调用prepend的效果是移动元素,而不是添加两次。为了解决这个问题,您需要使用clone方法创建想要插入的元素的副本。清单 7-8 显示了修改后的脚本。

清单 7-8 。克隆元素,以便可以多次将它们添加到文档中

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

        var orchidElems = $("<div class='dcell'/>")
            .append("<img src='orchid.png'/>")
            .append("<label for='orchid'>Orchid:</label>")
            .append("<input name='orchid' value='0' required />");

        var newElems = $("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />").add(orchidElems);

        newElems.css("border", "thick solid red");

        $("#row1").append(newElems);
        $("#row2").prepend(newElems.clone());
    });
</script>
...

现在元素被复制并插入到两个位置,如图图 7-4 所示。

9781430263883_Fig07-04.jpg

图 7-4 。克隆和插入元素

从 jQuery 对象插入

您可以使用appendToprependTo方法来改变元素之间的关系,如清单 7-9 所示。

清单 7-9 。使用 appendTo 方法

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

        var newElems = $("<div class='dcell'/>");

        $("img").appendTo(newElems);

        $("#row1").append(newElems);
    });
</script>
...

我创建了jQuery对象来包含文档中的一个新的div元素和img元素。然后我使用appendTo方法添加img元素作为div元素的子元素。你可以在图 7-5 中看到结果。如您所见,该脚本的效果是将img元素移动到新的div元素,我将它附加到了row1元素上。

9781430263883_Fig07-05.jpg

图 7-5 。使用 appendTo 方法

使用函数插入元素

你可以将一个function传递给appendprepend方法。这允许您动态地为jQuery对象选择的元素插入子元素,如清单 7-10 所示。

清单 7-10 。用函数动态添加子元素

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

        var orchidElems = $("<div class='dcell'/>")
            .append("<img src='orchid.png'/>")
            .append("<label for='orchid'>Orchid:</label>")
            .append("<input name='orchid' value='0' required />");

        var lilyElems = $("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />");

        $(orchidElems).add(lilyElems).css("border", "thick solid red");

        $("div.drow").append(function(index, html) {
            if (this.id == "row1") {
                return orchidElems;
            } else {
                return lilyElems;
            }
        });
    });
</script>
...

该函数为jQuery对象中的每个元素调用一次。传递给函数的参数是选择中元素的索引和正在处理的元素的 HTMLHTML 是一个字符串。此外,this变量的值被设置为适当的HTMLElement。该函数的结果将被追加或前置到正在处理的元素中。你可以返回一个 HTML 片段,一个或多个HTMLElement对象,或者一个jQuery对象。

在这个例子中,我准备为 lily 和 orchid 产品创建元素集,然后根据id属性的值从append函数返回它们。你可以在图 7-6 中看到结果。

9781430263883_Fig07-06.jpg

图 7-6 。基于函数动态插入元素

插入父元素和祖先元素

jQuery 为您提供了一组插入元素作为其他元素的父元素或祖先元素的方法。这被称为包装(因为一个元素被另一个元素包装)。表 7-3 描述了这些方法。

表 7-3 。包装元件的方法

方法 描述
wrap(HTML)``wrap(jQuery) 将指定的元素环绕在jQuery对象中的每个元素周围。
wrapAll(HTML)``wrapAll(jQuery) 将指定元素环绕在jQuery对象中的一组元素周围(作为一个组)。
wrapInner(HTML)``wrapInner(jQuery) 将指定元素环绕在jQuery对象中元素的内容周围。
wrap(function) wrapInner(function) 使用函数动态包装元素。

image 提示包装方法的补充是unwrap,我将在本章后面的“移除元素”一节中描述它。

执行包装时,可以将多个元素作为参数传递,但必须确保只有一个内部元素。否则,jQuery 想不出该怎么办。这意味着参数中的每个元素最多只能有一个父元素和一个子元素。清单 7-11 演示了wrap方法 的使用。

清单 7-11 。使用换行方法

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

        var newElem = $("<div/>").css("border", "thick solid red");
        $("div.drow").wrap(newElem);

    });
</script>
...

在这个脚本中,我创建了一个新的div元素,并使用css方法为 CSS border属性设置一个值。然后我使用wrap方法将div元素作为父元素插入到文档中的所有label元素中。在图 7-7 中可以看到效果。

9781430263883_Fig07-07.jpg

图 7-7 。使用 wrap 方法向元素添加父元素

作为参数传递给wrap方法的元素被插入到jQuery对象中的每个元素和它们当前的父元素之间。举个例子,这段 HTML:

...
<div class="dtable">
    <div id="row1" class="drow">
        ...
    </div>
    <div id="row2" class="drow">
        ...
    </div>
</div>
...

是这样转化的:

...
<div class="dtable">
    <div style="...style properties...">
        <div id="row1" class="drow">
            ...
        </div>
    </div>
    <div style="...style properties...">
        <div id="row2" class="drow">
            ...
        </div>
    </div>
</div>
...

在单个父级中将元素包装在一起

当您使用wrap方法时,新元素被克隆,jQuery对象中的每个元素都有自己的新父元素。你可以使用wrapAll方法为几个元素插入一个父元素,如清单 7-12 所示。

清单 7-12 。使用 wrapAll 方法

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

        var newElem = $("<div/>").css("border", "thick solid red");
        $("div.drow").wrapAll(newElem);

    });
</script>
...

这个脚本中唯一的变化是使用了wrapAll方法。你可以在图 7-8 中看到效果。

9781430263883_Fig07-08.jpg

图 7-8 。使用 wrapAll 方法

新元素用于将一个公共父元素插入到所选元素中,这样 HTML 就转换成这样:

...
<div class="dtable">
    <div style="...style properties...">
        <div id="row1" class="drow">
            ...
        </div>
        <div id="row2" class="drow">
        </div>
    </div>
</div>
...

使用wrapAll方法时要小心。如果所选元素尚未共享一个公共父元素,则新元素将作为父元素插入到第一个所选元素中。然后,jQuery 将所有其他选定的元素移动到第一个元素的兄弟元素。清单 7-13 包含了一个演示这种行为的脚本。

清单 7-13 。在没有公共父级的元素上使用 wrapAll

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

        var newElem = $("<div/>").css("border", "thick solid red");
        $("img").wrapAll(newElem);

    });
</script>
...

我选择了文档中的img元素,它们都没有一个公共的父元素。你可以在图 7-9 中看到这个脚本的效果。新的div元素已经作为 aster 图像的父元素插入到文档中,所有其他图像都作为兄弟元素插入。

9781430263883_Fig07-09.jpg

图 7-9 。对不共享公共父级的元素使用 wrapAll

包装元素的内容

wrapInner方法 将元素包装在jQuery对象中元素的内容周围,如清单 7-14 所示。

清单 7-14 。使用 wrapInner 方法

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

        var newElem = $("<div/>").css("border", "thick solid red");
        $(".dcell").wrapInner(newElem);

    });
</script>
...

wrapInner方法在jQuery对象中的元素和它们的直接子元素之间插入新元素。在脚本中,我选择了属于dcell类的元素,并用一个新的div元素包装它们的内容。你可以在图 7-10 中看到效果。

9781430263883_Fig07-10.jpg

图 7-10 。使用 wrapInner 方法

还可以通过使用append方法达到wrapInner方法的效果。仅供参考,下面是等效的脚本:

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

        var newElem = $("<div/>").css("border", "thick solid red");
        $(".dcell").each(function(index, elem) {
            $(elem).append(newElem.clone().append($(elem).children()));
        });

    });
</script>
...

我并不建议您使用这种方法(wrapInner方法更容易阅读,也更方便),但是我认为这是一个很好的例子,说明了如何使用 jQuery 以不同的方式执行相同的任务。

使用函数包装元素

您可以向wrapwrapInner方法传递一个函数来动态生成元素。为每个选定的元素调用该函数,并向其传递当前元素索引。特殊变量this被设置为要处理的元素。清单 7-15 中的脚本展示了如何动态包装元素。

清单 7-15 。动态包装元素

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

        $(".drow").wrap(function(index) {
            if ($(this).has("img[src*=rose]").length > 0) {
                return $("<div/>").css("border", "thick solid blue");;
            } else {
                return $("<div/>").css("border", "thick solid red");;
            }
        });

    });
</script>
...

在这个例子中,我使用一个带有wrap方法的函数,根据每个所选元素的后代定制新的父元素。你可以在图 7-11 中看到这个脚本的效果。

9781430263883_Fig07-11.jpg

图 7-11 。使用 wrap 方法和函数动态生成父元素

插入兄弟元素

jQuery 还提供了一组方法,将元素作为现有元素的兄弟元素插入到文档中,如表 7-4 中所述。

表 7-4 。插入同级元素的方法

方法 描述
after(HTML)``after(jQuery) 将指定元素作为下一个兄弟元素插入到jQuery对象中的每个元素。
before(HTML)``before(jQuery) 将指定元素作为前一个兄弟元素插入到jQuery对象的每个元素中。
insertAfter(HTML)``insertAfter(jQuery) 将元素插入到jQuery对象中,作为参数中指定的每个元素的下一个兄弟元素。
insertBefore(HTML)``insertBefore(jQuery) 将元素插入到jQuery对象中,作为参数中指定的每个元素的前一个兄弟元素。
after(function) before(function) 使用函数动态插入同级。

beforeafter方法遵循您在文档中插入其他类型元素时看到的相同模式。清单 7-16 包含了两种方法的演示。

清单 7-16 。使用 before 和 after 方法

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

        var orchidElems = $("<div class='dcell'/>")
            .append("<img src='orchid.png'/>")
            .append("<label for='orchid'>Orchid:</label>")
            .append("<input name='orchid' value='0' required />");

        var lilyElems = $("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />");

        $(orchidElems).add(lilyElems).css("border", "thick solid red");

        $("#row1 div.dcell").after(orchidElems);
        $("#row2 div.dcell").before(lilyElems);

    });
</script>
...

在这个脚本中,我为兰花和百合创建了新的元素集,并将它们与beforeafter方法一起使用,将它们作为兄弟元素插入到dcell类中的每个元素中。兰花元素作为row1中所有元素的下一个兄弟元素插入,而百合元素作为row2中所有元素的前一个兄弟元素插入。你可以在图 7-12 中看到这个脚本的效果。

9781430263883_Fig07-12.jpg

图 7-12 。使用 before 和 after 元素创建同级

从 jQuery 对象插入同级

insertAfterinsertBefore方法在jQuery对象中插入元素,作为方法参数中元素的下一个或上一个兄弟元素。这与afterbefore方法的功能相同,但是jQuery对象和参数之间的关系是相反的。清单 7-17 显示了这些方法的使用。该脚本创建了相同的效果,如图 7-12 所示。

清单 7-17 。使用 insertAfter 和 InsertBefore 方法

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

        var orchidElems = $("<div class='dcell'/>")
            .append("<img src='orchid.png'/>")
            .append("<label for='orchid'>Orchid:</label>")
            .append("<input name='orchid' value='0' required />");

        var lilyElems = $("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />");

        $(orchidElems).add(lilyElems).css("border", "thick solid red");

        orchidElems.insertAfter("#row1 div.dcell");
        lilyElems.insertBefore("#row2 div.dcell");
    });
</script>
...

使用函数插入兄弟节点

您可以使用带有afterbefore方法的函数动态插入兄弟元素,就像父元素和子元素一样。清单 7-18 包含了一个动态生成兄弟元素的例子。

清单 7-18 。用函数动态生成兄弟元素

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

        $("#row1 div.dcell").after(function(index, html) {
            if (index == 0) {
                return $("<div class='dcell'/>")
                    .append("<img src='orchid.png'/>")
                    .append("<label for='orchid'>Orchid:</label>")
                    .append("<input name='orchid' value='0' required />")
                    .css("border", "thick solid red");
            } else if (index == 1) {
                return $("<div class='dcell'/>")
                    .append("<img src='lily.png'/>")
                    .append("<label for='lily'>Lily:</label>")
                    .append("<input name='lily' value='0' required />")
                    .css("border", "thick solid red");
            }
        });

    });
</script>
...

在这个脚本中,当正在处理的元素的索引是01时,我使用index参数来生成兄弟元素。你可以在图 7-13 中看到这个脚本的效果。

9781430263883_Fig07-13.jpg

图 7-13 。使用函数添加同级元素

更换元件

您可以使用表 7-5 中描述的方法将一组元素替换为另一组元素。

表 7-5 。包装元件的方法

方法 描述
replaceWith(HTML)``replaceWith(jQuery) 用指定的内容替换jQuery对象中的元素。
replaceAll(jQuery) replaceAll(HTMLElement[]) jQuery对象中的元素替换参数指定的元素。
replaceWith(function) 使用函数动态替换jQuery对象中的元素。

除了jQuery对象和参数的角色颠倒之外,replaceWithreplaceAll方法的工作方式相同。清单 7-19 展示了这两种方法。

清单 7-19 。使用 replaceWith 和 replaceAll 方法

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

        var newElems = $("<div class='dcell'/>")
                    .append("<img src='orchid.png'/>")
                    .append("<label for='orchid'>Orchid:</label>")
                    .append("<input name='orchid' value='0' required />")
                    .css("border", "thick solid red");

        $("#row1").children().first().replaceWith(newElems);

        $("<img src='carnation.png'/>").replaceAll("#row2 img")
            .css("border", "thick solid red");

    });
</script>
...

在这个脚本中,我使用replaceWith方法用新内容替换row1 div元素的第一个子元素(这具有用兰花替换紫苑的效果)。我还使用了replaceAll方法来替换所有的img元素,它们是带有康乃馨图像的row2的后代。你可以在图 7-14 中看到这个脚本的效果。

9781430263883_Fig07-14.jpg

图 7-14 。用 replaceWith 和 replaceAll 方法替换内容

使用函数替换元素

您可以通过向replaceWith方法传递一个函数来动态替换元素。这个函数没有传递任何参数,但是变量this被设置为正在处理的元素。清单 7-20 提供了一个演示。

清单 7-20 。使用函数替换元素

...
<script type="text/javascript">
    $(document).ready(function() {
        $("div.drow img").replaceWith(function() {
            if (this.src.indexOf("rose") > -1) {
                return $("<img src='carnation.png'/>").css("border", "thick solid red");
            } else if (this.src.indexOf("peony") > -1) {
                return $("<img src='lily.png'/>").css("border", "thick solid red");
            } else {
                return $(this).clone();
            }
         });
    });
</script>
...

在这个脚本中,我根据src属性替换了img元素。如果src属性包含rose,那么我用一个显示carnation.png的元素替换img元素。如果src属性包含peony,那么我用一个显示lily.png的元素替换这个元素。两个替换元素都有一个红色边框来突出显示它们的位置。

对于所有其他元素,我返回正在处理的元素的克隆,其效果是用元素本身的副本替换元素。你可以在图 7-15 中看到效果。

9781430263883_Fig07-15.jpg

图 7-15 。使用函数替换元素

image 提示如果你不想替换一个元素,那么你可以简单的返回一个克隆体。如果不克隆元素,jQuery 最终会完全删除元素。当然,您可以通过缩小选择范围来避免这个问题,但这并不总是一个选项。

移除元素

为了补充插入和替换元素,jQuery 提供了一组从 DOM 中移除元素的方法,如表 7-6 中所述。

表 7-6 。移除元素的方法

方法 描述
detach() detach(selector) 从 DOM 中移除元素。与元素相关联的数据被保留。
empty() jQuery对象的每个元素中删除所有子节点。
remove() remove(selector) 从 DOM 中移除元素。随着元素被移除,与元素相关联的数据被破坏。
unwrap() 移除jQuery对象中每个元素的父元素。

清单 7-21 展示了如何使用remove元素从 DOM 中移除元素。

清单 7-21 。用 remove 方法从 DOM 中移除元素

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img[src*=daffodil], img[src*=snow]").parent().remove();
    });
</script>
...

这个脚本选择src属性包含daffodilsnowimg元素,获取它们的父元素,然后删除它们。如果你传递一个选择器给remove方法,你可以过滤你移除的元素,如清单 7-22 所示。

清单 7-22 。使用选择器过滤要移除的元素

...
<script type="text/javascript">
    $(document).ready(function() {
        $("div.dcell").remove(":has(img[src*=snow], img[src*=daffodil])");
    });
</script>
...

这两个脚本具有相同的效果,如图图 7-16 所示。

9781430263883_Fig07-16.jpg

图 7-16 。从 DOM 中移除元素

image 提示remove方法返回的jQuery对象包含原始的一组选定元素。换句话说,元素的移除不会反映在方法结果中。

分离元素

detach方法的工作方式与remove方法相同,只是保留了与元素相关的数据。我在第八章的中解释了数据与元素的关联,但是对于这一章来说,如果你想在文档的其他地方插入元素,知道这通常是最好的方法就足够了。清单 7-23 显示了正在使用的detach方法。

清单 7-23 。使用 detach 方法移除元素,同时保留关联的数据

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#row2").append($("img[src*=aster]").parent().detach());
    });
</script>
...

这个脚本分离出img元素的父元素,该元素的src属性包含aster。然后使用我在本章前面描述的append方法将元素插回到文档中。我倾向于不用这种方法,因为用append不用detach效果一样。您可以重写清单中的关键语句,如下所示:

...
$("#row2").append($("img[src*=aster]").parent());
...

你可以在图 7-17 中看到脚本的效果。

9781430263883_Fig07-17.jpg

图 7-17 。使用分离元素

清空元素

empty方法从jQuery对象的元素中移除任何后代和文本。元素本身被留在文档中,如清单 7-24 所示。

清单 7-24 。使用空方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#row1").children().eq(1).empty().css("border", "thick solid red");
    });
</script>
...

在这个脚本中,我选择索引 1 处的row1元素的子元素,并调用empty方法。为了让变化更加明显,我使用css方法添加了一个边框。你可以在图 7-18 中看到效果。

9781430263883_Fig07-18.jpg

图 7-18 。使用空方法

展开元素

unwrap方法移除jQuery对象中元素的父元素。所选元素成为其祖父元素的子元素。清单 7-25 显示了正在使用的unwrap方法。

清单 7-25 。使用展开方法

...
<script type="text/javascript">
    $(document).ready(function () {
        $("div.dcell").unwrap();
    });
</script>
...

在这个脚本中,我选择了属于dcell类的div元素,并调用了unwrap方法。这具有移除row1row2元素的效果,如图图 7-19 所示。

9781430263883_Fig07-19.jpg

图 7-19 。使用展开方法

摘要

在本章中,我向您展示了如何使用 jQuery 来操作 DOM。我向您展示了如何创建新元素,以及将元素(新的或现有的)作为子元素、父元素和兄弟元素插入 DOM 的许多不同方式。我还向您展示了如何在 DOM 中移动元素以及如何完全移除元素。在第八章中,我将向您展示如何使用 jQuery 来操作 DOM 中的元素。

八、操纵元素

在这一章中,我将向您展示如何使用 jQuery 操作元素,包括如何获取和设置属性,如何使用 jQuery 方便的方法处理类和 CSS 属性,以及如何获取和设置 HTML 和文本内容。我还向您展示了一个很好的特性,它允许您将数据与元素相关联。表 8-1 对本章进行了总结。

表 8-1 。章节总结

问题 解决办法 列表
jQuery对象中的第一个元素获取属性值 使用attr方法 one
jQuery对象中的每个元素获取属性值 一起使用eachattr方法 Two
jQuery对象中的所有元素设置属性 使用attr方法,可以选择使用函数 three
在一次操作中设置多个属性 对地图对象使用attr方法 4, 5
取消设置属性 使用removeAttr方法 six
获取或设置由HTMLElement对象定义的属性 使用与attr方法相对应的prop seven
控制元素所属的类 使用addClasshasClassremoveClass方法,可以选择使用函数 8–10
切换元素所属的类 使用toggleClass方法 11–16
设置style属性的内容 使用css方法 17–21
获取元素位置的详细信息 使用 CSS 属性特定的方法 22–24
获取或设置元素的文本或 HTML 内容 使用texthtml方法 25–27
获取或设置表单元素的值 使用val方法 28–30
将数据与元素相关联 使用data方法 Thirty-one

自上一版以来,JQUERY 发生了变化

jQuery 1.9/2.0 引入了一个新版本的css方法,它允许获取多个 CSS 属性的值——详见“获取多个 CSS 属性”一节。

jQuery 1.9/2.0 还强制分离了attrprop方法的角色。在早期版本中,jQuery 允许用户使用attr方法,而为了保持与 1.6 之前版本的向后兼容性,应该使用prop方法,而 1.6 之前的版本是添加了prop方法。

最后一个变化是 jQuery 的当前版本允许使用attr方法来设置input元素上的type属性值,前提是浏览器支持。小心使用这个特性,因为如果浏览器不支持它,jQuery 将抛出一个异常,这包括旧版本的 Internet Explorer。我的建议是,当你需要不同类型的input元素时,替换元素,完全避免这个问题。

使用属性和属性

您可以获取并设置jQuery对象中元素的属性值。表 8-2 显示了与属性相关的方法。

表 8-2 。使用属性的方法

方法 描述
attr(name) jQuery对象中的第一个元素获取具有指定名称的属性值
attr(name, value) 将具有指定名称的属性值设置为jQuery对象中所有元素的指定值
attr(map) jQuery对象中的所有元素设置在地图对象中指定的属性
attr(name, function) 使用函数为jQuery对象中的所有元素设置指定的属性
removeAttr(name) removeAttr(name[]) jQuery对象的所有元素中删除属性
prop(name) 返回jQuery对象中第一个元素的指定属性值
prop(name, value) prop(map) jQuery对象中的所有元素设置一个或多个属性值
prop(name, function) 使用函数为jQuery对象中的所有元素设置指定属性的值
removeProp(name) jQuery对象的所有元素中删除指定的属性

当用单个参数调用attr方法时,jQuery 从选择中的第一个元素返回指定属性的值。清单 8-1 包含了一个演示。

清单 8-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"/>
    <script type="text/javascript">
        $(document).ready(function() {
            var srcValue = $("img").attr("src");
            console.log("Attribute value: " + srcValue);
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required />
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required />
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required />
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

在这个脚本中,我选择了文档中所有的img元素,然后使用attr方法获取src属性的值。当您读取属性值时,attr方法的结果是一个字符串,我将它写到控制台。该脚本的控制台输出如下:

Attribute value: aster.png

each方法可以与attr结合使用,读取 jQuery 对象中所有元素的属性值。我在第五章中描述了each方法,而清单 8-2 展示了你如何在这种情况下使用它。

清单 8-2 。使用 each 和 attr 方法从多个对象读取属性值

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img").each(function(index, elem) {
            var srcValue = $(elem).attr("src");
            console.log("Attribute value: " + srcValue);
        });
    });
</script>
...

在这个脚本中,我将HTMLElement对象作为参数传递给函数,通过$函数创建一个新的jQuery对象。该对象只包含一个元素,非常适合于attr方法。该脚本的控制台输出如下:

Attribute value: aster.png
Attribute value: daffodil.png
Attribute value: rose.png
Attribute value: peony.png
Attribute value: primula.png
Attribute value: snowdrop.png

设置一个属性值

attr方法被用来设置一个属性值时,这个改变被应用到jQuery对象中所有元素的中。这与该方法的 read 版本形成对比,后者只从第一个元素返回一个值。清单 8-3 展示了如何设置一个属性。

清单 8-3 。设置属性

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img").attr("src", "lily.png");
    });
</script>
...

image 提示当设置一个值时,attr方法返回一个 jQuery 对象,这意味着你可以执行方法链接。

我选择了所有的img元素,并将src属性的值设置为lily.png。该值应用于所有选中元素的src属性,您可以在图 8-1 中看到效果。

9781430263883_Fig08-01.jpg

图 8-1 。为多个元素设置相同的属性值

设置多个属性

通过向attr方法传递一个对象,可以在一个方法调用中设置多个属性。该对象的属性被解释为属性名,属性值将被用作属性值。这就是所谓的地图对象??。清单 8-4 提供了一个演示。

清单 8-4 。使用地图对象设置多个元素

...
<script type="text/javascript">
    $(document).ready(function() {
        var attrValues = {
            src: "lily.png",
            style: "border: thick solid red"
        };

        $("img").attr(attrValues);
    });
</script>
...

在这个脚本中,我创建了一个 map 对象,它具有名为srcstyle的属性。我选择文档中的img元素,并将地图对象传递给attr值。你可以在图 8-2 中看到效果。

9781430263883_Fig08-02.jpg

图 8-2 。使用 attr 方法设置多个属性

image 提示虽然我已经在这个例子中明确设置了style属性,但是 jQuery 提供了一些简化 CSS 工作的方法。有关详细信息,请参见“使用 CSS”一节。

动态设置属性值

您可以通过向attr方法传递一个函数来定制分配给属性的值。清单 8-5 提供了一个演示。

清单 8-5 。使用函数设置属性值

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img").attr("src", function(index, oldVal) {
            if (oldVal.indexOf("rose") > -1) {
                return "lily.png";
            } else if ($(this).closest("#row2").length > 0) {
                return "carnation.png";
            }
        });
    });
</script>
...

传递给该函数的参数是正在处理的元素的索引和旧属性值。将this变量设置为正在处理的HTMLElement。如果你想改变属性,那么你的函数必须返回一个包含新值的字符串。如果没有返回结果,则使用现有值。在清单 8-5 中,我使用函数有选择地改变img元素显示的图像。你可以在图 8-3 中看到效果。

9781430263883_Fig08-03.jpg

图 8-3 。使用函数更改属性值

删除属性

你可以通过使用removeAttr方法删除(取消设置)属性,如清单 8-6 所示。

清单 8-6 。删除属性值

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

        $("img").attr("style", "border: thick solid red");
        $("img:odd").removeAttr("style");

    });
</script>
...

我使用attr方法设置style属性,然后使用removeAttr方法从奇数编号的元素中删除相同的属性。你可以在图 8-4 中看到效果。

9781430263883_Fig08-04.jpg

图 8-4 。从元素中移除属性

使用属性

对于每种形式的attr方法,都有相应的prop方法。不同之处在于,prop方法处理由HTMLElement对象定义的属性,而不是属性值。通常,属性和特性是相同的,但情况并非总是如此。一个简单的例子是class属性,它使用className属性在HTMLElement对象中表示。清单 8-7 显示了使用prop方法来读取这个属性。

清单 8-7 。使用 prop 方法读取属性值

...
<script type="text/javascript">
    $(document).ready(function() {
        $("*[class]").each(function(index, elem) {
            console.log("Element:" + elem.tagName + " " + $(elem).prop("className"));
        });
    });
</script>
...

在这个例子中,我选择了所有具有class属性的元素,并使用each方法来枚举它们。对于每个元素,我打印出typeclassName属性的值,产生以下控制台输出:

Element:DIV dtable
Element:DIV drow
Element:DIV dcell
Element:DIV dcell
Element:DIV dcell
Element:DIV drow
Element:DIV dcell
Element:DIV dcell
Element:DIV dcell

使用类

虽然您可以使用通用的属性方法来管理类,但是 jQuery 提供了一组更方便的方法。表 8-3 描述了这些方法。HTML 文档中类最常见的用途是让浏览器应用一组在style元素中定义的 CSS 属性。详见第七章。

表 8-3 。使用类的方法

方法 描述
addClass(name name) jQuery对象中的所有元素添加到指定的类中
addClass(function) jQuery对象中的元素动态分配给类
hasClass(name) 如果jQuery对象中至少有一个元素是指定类的成员,则返回true
removeClass(name name) 从指定的类中移除jQuery对象中的元素
removeClass(function) 从类中动态移除jQuery对象中的元素
toggleClass() 切换jQuery对象中的元素所属的所有类
toggleClass(boolean) 在一个方向上切换jQuery对象中的元素所属的所有类
toggleClass(name) toggleClass(name name) jQuery对象中的所有元素切换一个或多个命名类
toggleClass(name, boolean) 单向切换jQuery对象中所有元素的命名类
toggleClass(function, boolean) 动态切换一个jQuery对象中所有元素的类

可以使用addClass方法 将元素分配给类,使用removeClass方法从类中移除元素,并使用hasClass方法确定元素是否属于某个类。清单 8-8 展示了所有三种正在使用的方法。

清单 8-8 。添加、移除和测试类成员资格

...
<style type="text/css">
    img.redBorder {border: thick solid red}
    img.blueBorder {border: thick solid blue}
</style>
<script type="text/javascript">
    $(document).ready(function() {

        $("img").addClass("redBorder");
        $("img:even").removeClass("redBorder").addClass("blueBorder");

        console.log("All elements: " + $("img").hasClass("redBorder"));
        $("img").each(function(index, elem) {
            console.log("Element: " + $(elem).hasClass("redBorder") + " " + elem.src);
        });

    });
</script>
...

首先,我使用了一个style元素来定义两种基于类成员的样式。不一定要用类来管理 CSS,但是它们使得本章中演示变化的效果更加容易。

我选择文档中所有的img元素,并使用addClass方法将它们分配给redBorder类。然后我选择偶数编号的img元素,将它们从redBorder类中移除,并使用removeClass方法将它们分配给blueBorder类。

image 提示addClass方法不会从元素中移除任何现有的类;它只是在已经应用的类之外添加了新的类。

最后,我使用hasClass方法来测试所有img元素集合上的redBorder类(如果至少有一个元素是该类的成员,则返回true)和每个元素。你可以在图 8-5 中看到类成员的效果。

9781430263883_Fig08-05.jpg

图 8-5 。通过类成员关系应用样式

我测试类成员的脚本输出如下:

All elements: true
Element: falsehttp://www.jacquisflowershop.com/jquery/aster.png
Element: truehttp://www.jacquisflowershop.com/jquery/daffodil.png
Element: falsehttp://www.jacquisflowershop.com/jquery/rose.png
Element: truehttp://www.jacquisflowershop.com/jquery/peony.png
Element: falsehttp://www.jacquisflowershop.com/jquery/primula.png
Element: truehttp://www.jacquisflowershop.com/jquery/snowdrop.png

使用函数添加和移除类

通过向addClassremoveClass方法传递一个函数,您可以动态地决定应该从一组元素中添加或删除哪些类。清单 8-9 展示了使用addClass方法的函数。

清单 8-9 。将 addClass 方法与函数一起使用

...
<style type="text/css">
    img.redBorder {border: thick solid red}
    img.blueBorder {border: thick solid blue}
</style>
<script type="text/javascript">
    $(document).ready(function() {
        $("img").addClass(function(index, currentClasses) {
            if (index % 2 == 0) {
                return "blueBorder";
            } else {
                return "redBorder";
            }
        });
    });
</script>
...

该函数的参数是元素的索引和该元素所属的当前类集。对于类似的函数,jQuery 将变量this设置为正在处理的元素的HTMLElement对象。您返回希望元素加入的类。在这个例子中,我使用了index参数为blueBorderredBorder类分配替换元素。效果与图 8-5 中所示的效果相同。

您可以采用类似的方法从类中移除元素。你传递一个函数给removeClass方法,如清单 8-10 所示。

清单 8-10 。使用函数从类中移除元素

...
<style type="text/css">
    img.redBorder {border: thick solid red}
    img.blueBorder {border: thick solid blue}
</style>
<script type="text/javascript">
    $(document).ready(function() {

        $("img").filter(":odd").addClass("redBorder").end()
            .filter(":even").addClass("blueBorder");

        $("img").removeClass(function(index, currentClasses) {
            if ($(this).closest("#row2").length > 0
                &&currentClasses.indexOf("redBorder") > -1) {
                    return "redBorder";
            } else {
                return "";
            }
        });
    });
</script>
...

在这个脚本中,我传递给removeClass方法的function使用HTMLElement对象和当前的一组类从任何作为成员的img元素中删除redBorder类,该元素是 ID 为row2的元素的后代。你可以在图 8-6 中看到这个脚本的效果。

9781430263883_Fig08-06.jpg

图 8-6 。使用函数删除类

image 提示注意,当我不想删除任何类时,我返回空字符串。如果不返回值,jQuery 将从元素中移除所有的类。

切换类别

在最基本的形式中,切换一个类意味着将它添加到任何不是成员的元素中,并从任何是成员的元素中移除它。你可以通过将你想要切换的类名传递给toggleClass方法来达到这个效果,如清单 8-11 所示。

清单 8-11 。使用 toggleClass 方法

...
<style type="text/css">
    img.redBorder {border: thick solid red}
    img.blueBorder {border: thick solid blue}
</style>
<script type="text/javascript">
    $(document).ready(function() {

        $("img").filter(":odd").addClass("redBorder").end()
            .filter(":even").addClass("blueBorder");

        $("<button>Toggle</button>").appendTo("#buttonDiv").click(doToggle);

        function doToggle(e) {
            $("img").toggleClass("redBorder");
            e.preventDefault();
        };

    });
</script>
...

我通过将redBorder类应用于奇数编号的img元素并将blueBorder类应用于偶数编号的元素来开始这个脚本。然后我创建了一个button元素,并将其添加到idbuttonDiv的元素中。这将我的新button放在已经在页面上的旁边。我已经使用了click方法来指定当用户单击button时 jQuery 将调用的函数。这是 jQuery 对事件支持的一部分,我在第九章中有完整的描述。

点击按钮时执行的功能称为doToggle ,关键语句为

...
$("img").toggleClass("redBorder");
...

该语句选择文档中所有的img元素,并切换redBorder类。函数的参数和对preventDefault方法的调用在本章中并不重要,我会在第九章的中解释它们。你可以在图 8-7 中看到这个脚本的效果,尽管这种例子在你将文档加载到浏览器中并自己点击button时最有意义。

9781430263883_Fig08-07.jpg

图 8-7 。使用 toggleClass 方法切换类成员身份

如果你特别善于观察,你会注意到图中有些奇怪的地方。那些有红色边框的元素已经没有了,但是以蓝色边框开始的元素仍然有蓝色边框。正如所料,jQuery 从奇数编号的img元素中移除了redBorder类,并将其添加到偶数编号的元素中,但是添加了redBorder类的元素也是blueBorder的成员。在style元素中的redBorder之后定义了blueBorder样式,这意味着它的属性值具有更高的优先级,正如我在第三章中解释的那样。因此,类切换是有效的,但是你也必须考虑 CSS 的微妙之处。如果你想让红色边框显示出来,那么你可以颠倒样式的声明顺序,如清单 8-12 所示。

清单 8-12 。匹配样式声明以适应类切换

...
<style type="text/css">
    img.blueBorder {border: thick solid blue}
    img.redBorder {border: thick solid red}
</style>
<script type="text/javascript">
    $(document).ready(function() {
        $("img").filter(":odd").addClass("redBorder").end()
            .filter(":even").addClass("blueBorder");

        $("<button>Toggle</button>").appendTo("#buttonDiv").click(doToggle);

        function doToggle(e) {
            $("img").toggleClass("redBorder");
            e.preventDefault();
        };
    });
</script>
...

现在,当一个元素同时属于blueBorderredBorder类时,浏览器将使用border属性的redBorder设置。你可以在图 8-8 中看到这种变化的效果。

9781430263883_Fig08-08.jpg

图 8-8 。协调 CSS 声明顺序和类切换的效果

切换多个类别

您可以向toggleClass方法提供多个类名称,用空格分隔,每个名称将为所选元素切换。清单 8-13 显示了一个例子。

清单 8-13 。切换多个类别

...
<style type="text/css">
    img.blueBorder {border: thick solid blue}
    img.redBorder {border: thick solid red}
</style>
<script type="text/javascript">
    $(document).ready(function() {

        $("img").filter(":odd").addClass("redBorder").end()
            .filter(":even").addClass("blueBorder");

        $("<button>Toggle</button>").appendTo("#buttonDiv").click(doToggle);

        function doToggle(e) {
            $("img").toggleClass("redBorder blueBorder");
            e.preventDefault();
        };

    });
</script>
...

在这个例子中,我在所有的img元素上切换了redBorderblueBorder类。你可以在图 8-9 中看到效果。

9781430263883_Fig08-09.jpg

图 8-9 。切换多个元素

切换所有类别

您可以通过不带参数调用toggleClass方法来切换一组元素所属的所有类。这是一项巧妙的技术,因为 jQuery 存储了已经切换的类,因此它们可以被正确地应用和删除。清单 8-14 包含了一个使用该方法的例子。

清单 8-14 。切换选定元素的所有类别

...
<style type="text/css">
    img.blueBorder {border: thick solid blue}
    img.redBorder {border: thick solid red}
    label.bigFont {font-size: 1.5em}
</style>
<script type="text/javascript">
    $(document).ready(function() {

        $("img").filter(":odd").addClass("redBorder").end()
            .filter(":even").addClass("blueBorder");
        $("label").addClass("bigFont");

        $("<button>Toggle</button>").appendTo("#buttonDiv").click(doToggle);

        function doToggle(e) {
            $("img, label").toggleClass();
            e.preventDefault();
        };

    });
</script>
...

在这个例子中,我使用了addClass方法向imglabel元素添加类。当点击Toggle按钮时,我选择这些相同的元素,并不带任何参数地调用toggleClass方法。你得到一个特定的效果,如图图 8-10 所示。

9781430263883_Fig08-10.jpg

图 8-10 。切换元素的所有类

当您第一次单击该按钮时,选定元素的所有类都被关闭。jQuery 会记下哪些类被删除,以便在您再次单击按钮时可以重新应用它们。

向一个方向切换类别

您可以通过向toggleClass方法传递一个boolean参数来限制切换的执行方式。如果你通过了false,等级只会被移除,如果你通过了true,等级只会被增加。清单 8-15 给出了一个例子。

清单 8-15 。限制切换方向

...
<style type="text/css">
    img.blueBorder {border: thick solid blue}
    img.redBorder {border: thick solid red}
</style>
<script type="text/javascript">
    $(document).ready(function() {

        $("img").filter(":odd").addClass("redBorder").end()
            .filter(":even").addClass("blueBorder");

        $("<button>Toggle On</button>").appendTo("#buttonDiv").click(doToggleOn);
        $("<button>Toggle Off</button>").appendTo("#buttonDiv").click(doToggleOff);

        function doToggleOff(e) {
            $("img, label").toggleClass("redBorder", false);
            e.preventDefault();
        };
        function doToggleOn(e) {
            $("img, label").toggleClass("redBorder", true);
            e.preventDefault();
        };
    });
</script>
...

我在文档中添加了两个button元素,每个元素将只在一个方向上切换redBorder类。一旦其中一个button元素被点击,它将没有进一步的影响,直到另一个button也被点击(因为每个button只在一个方向切换类)。你可以在图 8-11 中看到效果。

9781430263883_Fig08-11.jpg

图 8-11 。单向切换类别

动态切换类别

您可以通过向toggleClass方法传递一个函数来决定应该为元素动态地切换哪些类。清单 8-16 提供了一个简单的演示。

清单 8-16 。用函数切换类

...
<style type="text/css">
    img.blueBorder {border: thick solid blue}
    img.redBorder {border: thick solid red}
</style>
<script type="text/javascript">
    $(document).ready(function() {

        $("img").addClass("blueBorder");
        $("img:even").addClass("redBorder");

        $("<button>Toggle</button>").appendTo("#buttonDiv").click(doToggle);

        function doToggle(e) {
            $("img").toggleClass(function(index, currentClasses) {
                if (index % 2 == 0) {
                   return "redBorder";
                } else {
                    return "";
                }
            });
            e.preventDefault();
        };
    });
</script>
...

我将blueBorder类应用于所有的img元素,将redBorder类应用于偶数的img元素。该函数的参数是您正在处理的元素的索引和它所属的当前类集。此外,this变量被设置为当前元素的HTMLElement对象。该函数的结果是应该切换的类的名称。如果您不想切换元素的任何类,那么您返回空字符串(不返回元素的结果将切换它的所有类)。你可以在图 8-12 中看到清单创建的效果。

9781430263883_Fig08-12.jpg

图 8-12 。动态切换类别

使用 CSS

在前面的例子中,我使用了基本的属性方法来设置style属性的值,以定义一组元素的 CSS 属性值。jQuery 提供了一组便利的元素,使得处理 CSS 更加容易。表 8-4 描述了这些方法中最广泛使用的css

表 8-4 。css 方法

方法 描述
css(name) jQuery对象中的第一个元素获取指定属性的值
css(names) 获取多个 CSS 属性的值,以数组形式表示
css(name, value) 设置jQuery对象中所有元素的特定属性值
css(map) 使用地图对象为jQuery对象中的所有元素设置多个属性
css(name, function) 使用函数为jQuery对象中的所有元素设置指定属性的值

image 提示这些方法操作单个元素的style属性。如果你想使用在一个style元素中定义的样式,那么你应该使用本章前面描述的与类相关的方法。

获取和设置单个 CSS 值

要读取 CSS 属性的值,需要将属性名传递给css方法。您收到的只是来自jQuery对象中第一个元素的值。但是,当您设置属性时,更改将应用于所有元素。清单 8-17 展示了css属性的基本用法。

清单 8-17 。使用 css 方法获取和设置 CSS 属性值

...
<script type="text/javascript">
    $(document).ready(function() {
        var sizeVal = $("label").css("font-size");
        console.log("Size: " + sizeVal);
        $("label").css("font-size", "1.5em");
    });
</script>
...

image 提示虽然我使用了实际的属性名(font-size)而不是由HTMLElement对象(fontSize)定义的 camel-case 属性名,但是 jQuery 很高兴两者都支持。

在这个脚本中,我选择了所有的label元素,并使用css方法获取font-size属性的值,并将其写入控制台。然后,我再次选择所有的label元素,并对它们应用相同属性的新值。

该脚本的输出如下:

Size: 16px

image 提示将属性设置为空字符串("")具有从元素的style属性中移除属性的效果。

获取多个 CSS 属性

通过向css方法传递一个属性名数组,可以获得多个 CSS 属性的值。此方法返回一个对象,该对象具有数组中每个名称的属性,并且该对象中每个属性的值被设置为所选内容中第一个元素的相应 CSS 属性的值。在清单 8-18 的中,您可以看到我是如何使用css方法来获取三个 CSS 属性的值的。

清单 8-18 。使用 css 方法获取多个 CSS 属性值

...
<script type="text/javascript">
    $(document).ready(function () {
        var propertyNames = ["font-size", "color", "border"];
        var cssValues = $("label").css(propertyNames);
        for (var i = 0; i < propertyNames.length; i++) {
            console.log("Property: " + propertyNames[i]
                + " Value: " + cssValues[propertyNames[i]]);
        }
    });
</script>
...

image 这个版本的css方法是在 jQuery 1.9/2.0 中引入的。

我创建了一个数组,包含我感兴趣的三个 CSS 属性的名称:font - sizecolorborder。我将这个数组传递给css方法,并接收一个包含我想要的值的对象。该目标可以表示如下:

{font-size: "16px", color: "rgb(0, 0, 0)", border: "0px none rgb(0, 0, 0)"}

为了处理对象,我遍历属性名数组并读取相应的属性值,产生以下控制台输出:

Property: font-size Value: 16px
Property: color Value: rgb(0, 0, 0)
Property: border Value: 0px none rgb(0, 0, 0)

设置多个 CSS 属性

您可以用两种不同的方式设置多个属性。第一种是简单地通过链接对css方法的调用,如清单 8-19 所示。

清单 8-19 。链接对 css 方法的调用

...
<script type="text/javascript">
    $(document).ready(function() {
        $("label").css("font-size", "1.5em").css("color", "blue");
    });
</script>
...

在这个脚本中,我设置了font-sizecolor属性的值。你可以使用一个地图对象达到同样的效果,如清单 8-20 所示。map 对象遵循与我在前面部分中使用css方法获取多个属性值时收到的对象相同的模式。

清单 8-20 。使用地图对象设置多个值

...
<script type="text/javascript">
    $(document).ready(function() {
        var cssVals = {
            "font-size": "1.5em",
            "color": "blue"
        };

        $("label").css(cssVals);
    });
</script>
...

这两个脚本都创建了如图 8-13 所示的效果。

9781430263883_Fig08-13.jpg

图 8-13 。设置多个属性

设置相对值

css方法可以接受相对值,这些值是以+=-=为前缀的数值,可以在当前值上加或减。这项技术只能用于以数字单位表示的 CSS 属性。清单 8-21 展示了。

清单 8-21 。在 css 方法中使用相对值

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

        $("label:odd").css("font-size", "+=5")
        $("label:even").css("font-size", "-=5")

    });
</script>
...

这些值被假定为与读取属性值时返回的单位相同。在这种情况下,我将奇数编号的label元素的字体大小增加了 5 个像素,并将偶数编号的label元素的字体大小减少了相同的量。你可以在图 8-14 中看到效果。

9781430263883_Fig08-14.jpg

图 8-14 。使用相对值

使用函数设置属性

你可以通过向css方法传递一个函数来动态设置属性值,如清单 8-22 所示。传递给函数的参数是元素的索引和属性的当前值。将元素的this变量设置为HTMLElement对象,然后返回想要设置的值。

清单 8-22 。用函数设置 CSS 值

...
<script type="text/javascript">
    $(document).ready(function() {
        $("label").css("border", function(index, currentValue) {
            if ($(this).closest("#row1").length > 0) {
                return "thick solid red";
            } else if (index % 2 == 1) {
                return "thick double blue";
            }
        });
    });
</script>
...

你可以在图 8-15 中看到这个脚本的效果。

9781430263883_Fig08-15.jpg

图 8-15 。用函数设置 CSS 属性值

使用特定于属性的 CSS 便利方法

除了css方法之外,jQuery 还定义了许多方法,可以用来获取或设置常用的 CSS 属性以及从中派生的信息。表 8-5 描述了这些方法。

表 8-5 。使用特定 CSS 属性的方法

方法 描述
height() 获取jQuery对象中第一个元素的高度,以像素为单位
height(value) 设置jQuery对象中所有元素的高度
innerHeight() 获取jQuery对象中第一个元素的内部高度(这是包括填充但不包括边框和边距的高度)
innerWidth() 获取jQuery对象中第一个元素的内部宽度(这是包括填充但不包括边框和边距的宽度)
offset() 返回jQuery对象中第一个元素相对于文档的坐标
outerHeight(boolean) 获取jQuery对象中第一个元素的高度,包括填充和边框;该参数确定是否包括边距
outerWidth(boolean) 获取jQuery对象中第一个元素的宽度,包括填充和边框;该参数确定是否包括边距
position() 返回jQuery对象中第一个元素相对于偏移量的坐标
scrollLeft() scrollTop() 获取jQuery对象中第一个元素的水平或垂直位置
scrollLeft(value) scrollTop(value) 设置jQuery对象中所有元素的水平或垂直位置
width() 获取jQuery对象中第一个元素的宽度
width(value) 设置jQuery对象中所有元素的宽度
height(function) width(function) 使用函数设置jQuery对象中所有元素的宽度或高度

这些方法中的大多数是不言而喻的,但是有几个值得解释。来自offsetposition方法的结果是一个具有topleft属性的对象,指示元素的位置。清单 8-23 提供了一个使用position方法 的演示。

清单 8-23 。使用位置方法

...
<script type="text/javascript">
    $(document).ready(function() {
        var pos = $("img").position();
        console.log("Position top: " + pos.top + " left: " + pos.left);
    });
</script>
...

此脚本写出方法返回的对象的 top 和 left 属性值。结果如下:

Position top: 108.078125 left: 18

使用函数设置宽度和高度

您可以通过向widthheight方法传递一个函数来动态设置一组元素的宽度和高度。此方法的参数是元素的索引和当前属性值。正如您现在所期望的,变量this被设置为当前元素的HTMLElement,您返回您想要赋值的值。清单 8-24 提供了一个例子。

清单 8-24 。使用函数设置元素的高度

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#row1 img").css("border", "thick solid red")
            .height(function(index, currentValue) {
                return (index + 1) * 25;
            });
    });
</script>
...

在这个脚本中,我使用索引值作为高度的乘数。你可以在图 8-16 中看到效果。

9781430263883_Fig08-16.jpg

图 8-16 。使用函数设置元素的高度

使用元素内容

到目前为止,我在本章中描述的方法是对元素定义的属性进行操作的,但是 jQuery 也提供了处理元素内容的方法,如表 8-6 中所述。

表 8-6 。处理元素内容的方法

方法 描述
text() 获取 jQuery 对象中所有元素及其后代的组合文本内容
text(value) 设置jQuery对象中每个元素的内容
html() 获取jQuery对象中第一个柠檬的 html 内容
html(value) 设置jQuery对象中每个元素的 HTML 内容
text(function) html(function) 使用函数设置文本或 HTML 内容

与 jQuery 不同的是,当您使用不带参数的text方法时,您收到的结果是从所有选定元素的生成的,而不仅仅是第一个元素。html方法与 jQuery 的其余部分更加一致,只返回第一个元素的内容,如清单 8-25 所示。

清单 8-25 。使用 html 方法读取元素内容

...
<script type="text/javascript">
    $(document).ready(function() {
        var html = $("div.dcell").html();
        console.log(html);
    });
</script>
...

这个脚本使用html方法读取由div.dcell选择器匹配的第一个元素的 HTML 内容。这被写入控制台,产生以下结果。请注意,元素本身的 HTML 不包括在内。

<img src="aster.png">
<label for="aster">Aster:</label>
<input name="aster" value="0" required="">

设置元素含量

您可以使用htmltext方法设置元素的内容。我的花店示例文档没有任何文本内容,所以清单 8-26 展示了如何使用html方法。

清单 8-26 。使用 html 方法设置元素内容

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#row2 div.dcell").html($("div.dcell").html());
    });
</script>
...

这个脚本设置了dcell类中div元素的 HTML 内容,这些元素是row2元素的后代。对于内容,我使用了html方法从第一个div.dcell元素读取 HTML。这具有将布局中的下一行单元格设置为 aster 内容的效果,如图 8-17 中的所示。

9781430263883_Fig08-17.jpg

图 8-17 。用 html 方法设置元素的内容

使用功能设置元素内容

与本章中的许多其他方法一样,您可以使用带有函数的htmltext方法来动态设置内容。在这两种情况下,参数都是jQuery对象中元素的索引和当前文本或 HTML 内容。this变量被设置为元素的HTMLElement对象,您返回想要设置为函数结果的值。清单 8-27 展示了如何通过文本方法使用一个函数。

清单 8-27 。使用函数设置文本内容

...
<script type="text/javascript">
    $(document).ready(function() {
        $("label").css("border", "thick solid red").text(function(index, currentValue) {
            return "Index " + index;
        });
    });
</script>
...

在这个脚本中,我使用索引值设置了label元素的文本内容(我还使用了css方法为我更改的元素添加了一个边框)。你可以在图 8-18 中看到结果。

9781430263883_Fig08-18.jpg

图 8-18 。使用函数设置文本内容

使用表单元素

您可以使用val方法获取和设置form元素(如input)的值,该方法在表 8-7 中有描述。

表 8-7 。瓦尔法

方法 描述
val() 返回jQuery对象中第一个元素的值
val(value) 设置jQuery对象中所有元素的值
val(function) 使用函数设置jQuery对象中元素的值

清单 8-28 展示了如何使用val方法从jQuery对象的第一个元素中获取值。在这个脚本中,我使用了each方法,这样我就可以枚举文档中的一组input元素的值。

清单 8-28 。使用 val 方法从输入元素中获取值

...
<script type="text/javascript">
    $(document).ready(function() {
       $("input").each(function(index, elem) {
            console.log("Name: " + elem.name + " Val: " + $(elem).val());
       });
    });
</script>
...

我将这些值写入控制台,控制台会产生以下输出:

Name: aster Val: 0
Name: daffodil Val: 0
Name: rose Val: 0
Name: peony Val: 0
Name: primula Val: 0
Name: snowdrop Val: 0

设置表单元素值

您可以使用val方法来设置jQuery对象中所有元素的值,只需将您想要的值作为参数传递给该方法。清单 8-29 演示了。

清单 8-29 。使用 val 方法设置元素值

...
<script type="text/javascript">
    $(document).ready(function () {
        $("<button>Set Values</button>").appendTo("#buttonDiv")
            .click(function (e) {
                $("input").val(100);
                e.preventDefault();
            })
    });
</script>
...

在这个脚本中,我在文档中添加了一个button元素,并指定了一个单击时调用的内嵌函数。该函数选择文档中所有的input元素,并使用val方法将它们的值设置为100。你可以在图 8-19 中看到效果。(对preventDefault方法的调用会阻止浏览器将 HTML 表单发送回 web 服务器——我会在第九章中详细解释 jQuery 如何支持事件)。

9781430263883_Fig08-19.jpg

图 8-19 。使用 val 方法设置输入元素值

使用函数设置表单元素值

正如您现在所期望的,您也可以使用函数通过val方法来设置值。方法的参数是元素的索引和元素的当前值。变量this被设置为代表被处理元素的HTMLElement对象。通过以这种方式使用val方法,你可以动态地设置新值,如清单 8-30 所示。

清单 8-30 。对函数使用 val 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("input").val(function(index, currentVal) {
            return (index + 1) * 100;
        });
    });
</script>
...

在本例中,我根据 index 参数设置了值。你可以在图 8-20 中看到效果。

9781430263883_Fig08-20.jpg

图 8-20 。使用 val 方法和函数动态设置值

将数据与元素相关联

jQuery 允许您将任意数据与一个元素相关联,然后您可以对其进行测试并在以后进行检索。表 8-8 描述了与该特性相关的方法。

表 8-8 。处理任意元素数据的方法

方法 描述
data(key, value) data(map) 将一个或多个键/值对与jQuery对象中的元素相关联
data(key) jQuery对象的第一个元素中检索与指定键相关联的值
data() jQuery对象的第一个元素中检索键/值对
removeData(key) jQuery对象的所有元素中删除与指定键相关的数据
removeData() jQuery对象的所有元素中删除所有数据项

清单 8-31 演示了数据值的设置、测试、读取和删除。

清单 8-31 。使用元素数据

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

        // set the data
        $("img").each(function () {
           $(this).data("product", $(this). siblings("input[name]").attr("name"));
        });

        // find elements with the data and read the values
        $("*").filter(function() {
            return $(this).data("product") != null;
        }).each(function() {
           console.log("Elem: " + this.tagName + " " + $(this).data("product"));
        });

        // remove all data
        $("img").removeData();

    });
</script>
...

image 注意当您使用 clone 方法时,您与元素关联的数据将从新复制的元素中删除,除非您明确告诉 jQuery 您想要保留它。关于clone方法以及如何保存数据的详细信息,参见第七章。

这个脚本有三个阶段。首先,我使用data方法将一个数据项与product键关联起来。我通过从每个img元素导航到具有name属性的input兄弟元素来获取数据。

在第二阶段,我选择文档中的所有元素,然后使用filter方法找到那些具有与product键相关联的值的元素。然后,我使用each方法来枚举这些元素,并将数据值写入控制台。这是重复的,但是我想展示选择包含数据的元素的最佳技术。没有专用的选择器或方法,所以您必须使用filter方法和一个函数。

最后,我使用removeData从所有的img元素中删除所有的数据。该脚本在控制台上产生以下输出:

Elem: IMG aster
Elem: IMG daffodil
Elem: IMG rose
Elem: IMG peony
Elem: IMG primula
Elem: IMG snowdrop

摘要

在这一章中,我向你展示了在 DOM 中操作元素的不同方法。我向您展示了如何获取和设置属性,包括处理类和 CSS 属性的 jQuery 便利方法。我还展示了如何获取和设置元素的文本或 HTML 内容,以及 jQuery 如何支持任意数据与元素的关联。

九、处理事件

在这一章中,我描述了 jQuery 对事件的支持。如果你不熟悉事件,那么我在第二章中提供了一个关于它们如何工作以及它们如何通过 DOM(域对象模型)传播的简要概述。jQuery 提供了一些有用的与事件相关的特性,其中我最喜欢的是在元素被添加到 DOM 时自动将事件处理函数与元素关联起来的能力。表 9-1 对本章进行了总结。

表 9-1 。章节总结

问题 解决办法 列表
注册一个函数来处理一个或多个事件 使用绑定方法或一种速记方法 1–4, 19, 20, 23
禁止事件的默认操作 使用 Event.preventDefault 方法或使用 bind 方法,而不指定处理函数 5–6
从元素中移除事件处理函数 使用解除绑定方法 7–9
创建一个处理函数,该函数只对与之关联的每个元素执行一次 使用的方法 Ten
当元素被添加到文档中时,自动将事件处理函数应用于元素 使用上的方法 11–13
移除使用 live 方法创建的处理程序 使用的方法 Fourteen
将自动添加的处理程序应用于 DOM 中的特定元素 使用委托取消委托的方法 Fifteen
手动调用元素的事件处理函数 使用触发器触发器处理程序方法或一种速记方法 16–18, 21, 22

自上一版以来,JQUERY 发生了变化

jQuery 1.9/2.0 移除了livedie方法。通过onoff方法可以获得相同的功能,这在“执行实时事件绑定”一节中有描述

使用trigger方法时,对focusblur事件的发送顺序进行了一些幕后更改,以便它们更好地遵循用户触发事件时看到的顺序。有关如何使用trigger方法的详细信息,请参见“手动调用事件处理程序”一节。

一个相关的变化是,当您在复选框或单选按钮input元素上触发click事件时,事件处理程序将接收元素的新状态,这与用户更改元素状态所产生的效果一致。在以前版本的 jQuery 中,处理程序会接收旧的状态。

我在本书的上一版中省略了一些 jQuery 特性,因为它们被标记为不推荐使用。jQuery 最新版本中的变化不会影响本章的内容,但是如果您在jquery.com上找到了对它们的引用并开始在项目中使用它们,那么这些变化是值得一提的:jQuery Event对象中已经删除了attrChangeattrNamerelatedNodesrcElement属性;不再支持hover伪事件(但是我在“使用事件速记方法”一节中描述的hover方法不受影响);在同一个事件的两个事件处理函数之间交替的toggle方法已经被删除。

处理事件

jQuery 提供了一组方法,允许您注册在感兴趣的元素上触发指定事件时调用的函数。表 9-2 描述了这些方法。

表 9-2 。处理事件的方法

方法 描述
bind(eventType, function) bind(eventType, data, function) 向带有可选数据项的 jQuery 对象中的元素添加事件处理程序
bind(eventType, boolean) 创建一个总是返回 false 的默认处理程序,阻止默认操作。布尔参数控制事件冒泡
bind(map) jQuery 对象中的所有元素添加一组基于 map 对象的事件处理程序
one(eventType, function) one(eventType, data, function) 向带有可选数据项的 jQuery 对象中的每个元素添加事件处理程序;一旦元素被执行,处理程序将被注销。
unbind() 移除 jQuery 对象中所有元素的所有事件处理程序
unbind(eventType) jQuery 对象的所有元素中删除先前注册的事件处理程序
unbind(eventType, boolean) jQuery 对象的所有元素中删除先前注册的始终为假的处理程序
unbind(Event) 使用事件对象删除事件处理程序

各种风格的bind方法允许您指定一个当事件被触发时将被调用的函数,由于这是 jQuery,该函数用于调用bind方法的jQuery对象中的所有元素。清单 9-1 显示了一个简单的例子。

清单 9-1 。使用 bind 方法注册事件处理函数

<!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"/>
    <script type="text/javascript">
        $(document).ready(function() {
                  function handleMouseEnter(e) {
                $(this).css({
                    "border": "thick solid red",
                    "opacity": "0.5"
                });
            };

            function handleMouseOut(e) {
                $(this).css({
                    "border": "",
                    "opacity": ""
                });
            }

            $("img").bind("mouseenter", handleMouseEnter)
                .bind("mouseout", handleMouseOut);
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required />
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required />
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required />
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

在清单 9-1 中,我选择了文档中所有的img元素,并使用bind方法为mouseentermouseout事件注册处理函数。这些处理程序使用css方法来设置borderopacity属性的值。当用户将鼠标指针移动到其中一个img元素上时,会绘制边框,图像会变得更加透明,当指针移开时,图像会恢复到原来的状态。

当 jQuery 调用处理程序函数时,this变量被设置为处理程序所附加的元素。传递给 handler 函数的对象是 jQuery 自己的Event对象,不同于 DOM 规范定义的Event对象。表 9-3 描述了 jQuery Event对象的属性和方法。

表 9-3 。jQuery 事件对象的成员

名字 描述 返回
currentTarget 获取当前正在调用其侦听器的元素 HTMLElement
Data 获取注册处理程序时传递给 bind 方法的可选数据;有关详细信息,请参见“注册一个函数来处理多种事件类型”一节 Object
isDefaultPrevented() 如果调用了 preventDefault 方法,则返回 true Boolean
isImmediatePropagationStopped() 如果调用了停止立即传播方法,则返回真值 Boolean
isPropagationStopped() 如果调用了停止传播方法,则返回真值 Boolean
originalEvent 返回原始 DOM 事件对象 Event
pageX pageY 返回相对于文档左边缘的鼠标位置 number
preventDefault() 阻止执行与事件相关联的默认操作 void
relatedTarget 对于鼠标事件,返回相关元素;这取决于触发了哪个事件 HTMLElement
Result 返回处理此事件的最后一个事件处理程序的结果 Object
stopImmediatePropagation() 防止为此事件调用任何其他事件处理程序 void
stopPropagation() 防止事件冒泡,但允许附加到当前目标元素的处理程序接收事件 void
Target 获取触发事件的元素 HTMLElement
timeStamp 获取事件的触发时间 number
Type 获取事件的类型 string
Which 返回为鼠标和键盘事件按下的按钮或按键 number

jQuery Event对象还定义了标准 DOM Event对象的大多数属性。因此,对于几乎所有情况,您都可以将 jQuery Event对象视为具有 DOM 标准定义的功能的超集。

注册一个函数来处理多个事件类型

一种常见的技术是使用一个函数来处理两种或多种事件。这些事件通常以某种方式相关联,例如mouseentermouseout事件。使用bind方法时,可以在第一个参数中指定多个事件类型,用空格分隔。清单 9-2 展示了这种方法。

清单 9-2 。注册一个函数来处理多个事件类型

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

        function handleMouse(e) {
            var cssData = {
                "border": "thick solid red",
                "opacity": "0.5"
            }
            if (event.type == "mouseout") {
                cssData.border = "";
                cssData.opacity = "";
            }
            $(this).css(cssData);
        }

        $("img").bind("mouseenter mouseout", handleMouse);

    });
</script>
...

在这个脚本中,我使用了一个对bind方法的调用来指定文档中所有img元素的mouseentermouseout事件应该由handleMouse函数来处理。当然,您也可以使用单个函数并链接绑定调用,如下所示:

...
$("img").bind("mouseenter", handleMouse).bind("mouseout", handleMouse);
...

还可以使用 map 对象注册处理程序。对象的属性是事件的名称,它们的值是事件被触发时将被调用的函数。清单 9-3 展示了一个地图对象和bind方法的使用。

清单 9-3 。使用映射对象注册事件处理程序

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

        $("img").bind({
            mouseenter: function() {
                $(this).css("border", "thick solid red");
            },
            mouseout: function() {
                $(this).css("border", "");
            }
        });

    });
</script>
...

在清单 9-3 中,我已经定义了内嵌的处理函数,作为 map 对象的一部分。bind方法使用我指定的函数作为事件的处理程序,这些事件对应于 map 对象中的属性名称。

向事件处理函数提供数据

您可以将一个对象传递给bind方法,然后 jQuery 将通过Event.data属性将该对象提供给处理函数。当使用单个函数处理来自不同元素集的事件时,这可能很有用。data值可以帮助确定需要什么样的响应。清单 9-4 展示了如何定义和使用数据值。

清单 9-4 。通过 bind 方法将数据传递给事件处理函数

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

        function handleMouse(e) {
            var cssData = {
                "border": "thick solid " + e.data,
            }
            if (event.type == "mouseout") {
                cssData.border = "";
            }
            $(this).css(cssData);

        }

        $("img:odd").bind("mouseenter mouseout", "red", handleMouse);
        $("img:even").bind("mouseenter mouseout", "blue", handleMouse);
    });
</script>
...

我使用了bind方法的可选参数来指定当mouseenter事件被触发时应该显示哪种颜色的边框。对于奇数编号的img元素,边界将为red,对于偶数编号的元素,边界将为blue。在事件处理函数中,我使用Event.data属性读取数据,并用它来创建 CSS(级联样式表)border属性的值。你可以在图 9-1 中看到效果。

9781430263883_Fig09-01.jpg

图 9-1 。通过 bind 方法将数据传递给处理函数

取消默认操作

正如我在第二章的中提到的,一些事件在某些元素上被触发时会有一个默认的动作。一个很好的例子是当用户点击一个按钮,它的属性是 ??。如果button包含在form元素中,浏览器的默认动作是提交表单。为了防止默认动作被执行,你可以在Event对象上调用preventDefault方法,如清单 9-5 所示。

清单 9-5 。阻止对事件的默认操作

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

        $("button:submit").bind("click", function(e) {
            e.preventDefault();
        });

    });
</script>
...

这个脚本为所有button元素上的click事件设置了一个处理函数,这些元素的type属性被设置为submit。该函数只包含一个对preventDefault方法的调用,这意味着单击按钮不会做任何事情,因为默认操作是禁用的,并且处理函数没有设置任何替代选项。

通常你想取消默认动作,这样你就可以执行一些其他的活动——例如,阻止浏览器提交表单,因为你想用 Ajax 来完成(这是第十四章和第十五章的主题)。不要像我在清单 9-5 中那样写一行函数,你可以使用不同版本的bind方法,如清单 9-6 所示。

清单 9-6 。使用 bind 方法创建阻止默认操作的处理程序

...
<script type="text/javascript">
    $(document).ready(function() {
        $("button:submit").bind("click", false);
    });
</script>
...

第一个参数是您想要抑制其默认动作的一个或多个事件,第二个参数允许您指定是否应该阻止事件在 DOM 中冒泡(我在第二章的中解释了事件冒泡)。

删除事件处理函数

unbind方法从一个元素中移除一个处理函数。您可以通过不带参数调用unbind方法来解除与一个jQuery对象中所有元素的所有事件相关联的所有处理程序的绑定,如清单 9-7 所示。

清单 9-7 。取消绑定所有事件处理程序

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

        function handleMouse(e) {
            var cssData = {
                "border": "thick solid red",
                "opacity": "0.5"
            }
            if (event.type == "mouseout") {
                cssData.border = "";
                cssData.opacity = "";
            }
            $(this).css(cssData);
        }

        $("img").bind("mouseenter mouseout", handleMouse);

        $("img[src*=rose]").unbind();

    });
</script>
...

在清单 9-7 中,我为所有img元素的mouseentermouseout事件设置了一个处理程序,然后使用unbind方法移除img元素的所有处理程序,该元素的src属性包含rose。通过将您想要解除绑定的事件作为参数传递给unbind方法,您可以更有选择性,如清单 9-8 所示。

清单 9-8 。选择性解除事件绑定

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

        function handleMouse(e) {
            var cssData = {
                "border": "thick solid red",
                "opacity": "0.5"
            }
            if (event.type == "mouseout") {
                cssData.border = "";
                cssData.opacity = "";
            }
            $(this).css(cssData);
        }

        $("img").bind("mouseenter mouseout", handleMouse);

        $("img[src*=rose]").unbind("mouseout");

    });
</script>
...

在这个脚本中,我只解除了mouseout事件的绑定,而没有触动mouseenter事件的处理程序。

从事件处理函数中取消绑定

解除绑定的最后一个选项是从事件处理函数中进行。例如,如果您想要处理某个事件一定次数,这可能会很有用。清单 9-9 包含了一个简单的演示。

清单 9-9 。从事件处理程序中的事件解除绑定

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

        var handledCount = 0;

        function handleMouseEnter(e) {
            $(this).css("border", "thick solid red");
        }
        function handleMouseExit(e) {
            $(this).css("border", "");
            handledCount ++;
            if (handledCount == 2) {
                $(this).unbind(e);
            }
        }
        $("img").bind("mouseenter", handleMouseEnter).bind("mouseout", handleMouseExit)
    });
</script>
...

handleMouseEvent函数中,我每处理一次mouseout事件就增加一个计数器。在我处理了两次事件之后,我将Event对象传递给unbind方法,以取消该函数作为处理程序的注册。jQuery 从事件对象中找出它需要的细节。

执行一次处理程序

one方法允许您注册一个事件处理程序,该程序对于一个元素只执行一次,然后被删除。清单 9-10 提供了一个例子。

清单 9-10 。使用 one 方法注册单次事件处理函数

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

        function handleMouseEnter(e) {
            $(this).css("border", "thick solid red");
        };

        function handleMouseOut(e) {
            $(this).css("border", "");
        };

        $("img").one("mouseenter", handleMouseEnter).one("mouseout", handleMouseOut);

    });
</script>
...

我已经使用了one方法来注册mouseentermouseout事件的处理程序。当用户将鼠标移入和移出其中一个img元素时,将调用处理函数,然后该函数将被解除绑定(但只是针对那个元素;其他的仍然有处理程序,直到鼠标移到它们上面)。

执行实时事件绑定

bind方法的一个限制是,您的事件处理函数不与您添加到 DOM 的任何新元素相关联。清单 9-11 包含了一个例子。

清单 9-11 。设置事件处理程序后添加元素

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img").bind({
            mouseenter: function() {
                $(this).css("border", "thick solid red");
            },
            mouseout: function() {
                $(this).css("border", "");
            }
        });

        $("#row1").append($("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />"));
    });
</script>
...

在这个脚本中,我使用bind方法为所有img元素的mouseentermouseout事件设置处理程序。然后我使用append方法在文档中插入一些新元素,包括另一个img元素。当我使用bind方法时,这个新的img元素并不存在,我的处理函数也没有与之关联。这样做的结果是,我有六个img元素在鼠标悬停在它们上面时显示边框,而一个没有。

在一个像清单 9-11 中的这样简单的例子中,简单的答案是再次调用bind方法,但是很难记住不同类型的元素需要哪些处理程序。幸运的是,jQuery 为您提供了一组方法,当匹配选择器的新元素被添加到 DOM 时,这些方法会自动注册事件处理程序。表 9-4 描述了这些方法。

表 9-4 。自动注册事件处理程序的方法

方法 描述
on(events, selector, data, function) on (map, selector, data) 为现在或将来存在的元素定义事件处理程序
off(events, selector, function) off(map, selector) 移除使用 on 方法创建的事件处理程序
delegate(selector, eventType, function)``delegate(selector, eventType, data,``function) 将事件处理程序添加到与附加到 jQuery 对象中元素的选择器(现在或将来)相匹配的元素中
undelegate() undelegate(selector, eventType) 为指定的事件类型移除使用委托方法创建的事件处理程序

清单 9-12 显示了之前的例子更新为使用on方法 。变化很小,但影响很大。我添加到 DOM 中与选择器img匹配的任何元素都将在 map 对象中拥有函数,这些函数被设置为mouseentermouseout事件的处理程序。

清单 9-12 。使用 on 方法

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

        $(document).on({
            mouseenter: function () {
                $(this).css("border", "thick solid red");
            },
            mouseout: function () {
                $(this).css("border", "");
            }
        }, "img");

        $("#row1").append($("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />"));
    });
</script>
...

注意,我在从document创建的jQuery对象上调用了on方法。这确保了我的事件处理程序被应用于添加到 DOM 中任何地方的任何img元素。我可以通过更改初始选择器来缩小关注范围——例如,如果我只想将我的处理函数应用于添加到row1元素的img元素,我可以使用下面的调用来代替:

...
$("#row1").on(*...map...*, "img");
...

image 提示on方法不需要直接给元素添加处理函数。事实上,它在document对象上创建了一个事件处理程序,并寻找由匹配选择器的元素触发的事件。当它看到这样的事件时,它会触发事件处理程序。然而,实际上,更容易想象的是on方法努力为新元素添加句柄。

使用on方法时,可以指定多个事件。事件名由空格字符分隔,或者在地图对象的属性名中,或者在不使用地图时在第一个参数中,如清单 9-13 所示。

清单 9-13 。使用 on 方法指定多个事件

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

        function handleMouse(event) {
            if (event.type == "mouseenter") {
                $(this).css("border", "thick solid red");
            } else if (event.type == "mouseout") {
                $(this).css("border", "");
            }
        }

        $("#row1").on("mouseenter mouseout", "img", handleMouse);

        $("#row1").append($("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />"));
    });
</script>
...

在清单 9-13 中,我使用了不依赖于地图对象的on方法版本,指定handleMouse函数应该用于由所有img元素发出的mouseentermouseout事件,这些【】元素是row1元素的后代。

on方法的补充是off,它用于从现有元素中移除事件处理程序,并防止它们被用来响应新创建元素的事件。清单 9-14 展示了off方法 的使用。

清单 9-14 。使用关闭方法

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

        function handleMouse(event) {
            if (event.type == "mouseenter") {
                $(this).css("border", "thick solid red");
            } else if (event.type == "mouseout") {
                $(this).css("border", "");
            }
        }

        $("#row1").on("mouseenter mouseout", "img", handleMouse);

        $("#row1").off("mouseout", "img");

        $("#row1").append($("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />"));
    });
</script>
...

image 注意使用与onoff方法相同的选择器很重要;否则,off方法无法解除on的效果。

限制实时事件处理程序的 DOM 遍历

on方法的一个问题是,在执行处理函数之前,事件必须一直传播到document元素。您可以采取一种更直接的方法,使用delegate方法 ,它允许您指定事件监听器将位于文档中的什么位置。清单 9-15 提供了一个例子。

清单 9-15 。使用委托方法

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

        $("#row1").delegate("img", {
            mouseenter: function() {
                $(this).css("border", "thick solid red");
            },
            mouseout: function() {
                $(this).css("border", "");
            }
        });

        $("#row1").append($("<div class='dcell'/>")
            .append("<img src='carnation.png'/>")
            .append("<label for='carnation'>Carnation:</label>")
            .append("<input name='carnation' value='0' required />"));

        $("#row2").append($("<div class='dcell'/>")
            .append("<img src='lily.png'/>")
            .append("<label for='lily'>Lily:</label>")
            .append("<input name='lily' value='0' required />"));
    });

</script>
...

在清单 9-15 中,我使用delegate方法将监听器添加到 ID 为#row1的元素中,我指定的选择器与img元素相匹配。这样做的效果是,当源于img元素的mouseentermouseout事件传播到row1元素时,我的处理函数将被执行。当我向row1添加另一个img元素时,它会被我对delegate方法的调用自动覆盖,而当我向row2添加元素时却不是这样。

使用delegate方法的主要好处是速度,如果您有一个特别大而复杂的文档和许多事件处理程序,这可能会成为一个问题。通过将事件被拦截的点下推到文档中,可以减少事件在导致处理函数被调用之前在 DOM 中必须经过的距离。

image 提示要删除用delegate方法添加的处理程序,必须使用undelegateoff方法仅适用于on方法。

手动调用事件处理程序

您可以使用表 9-5 中描述的方法手动调用元素上的事件处理函数。

表 9-5 。手动调用事件处理程序的方法

方法 描述
trigger(eventType) 在一个 jQuery 对象的所有元素上触发指定事件类型的处理函数
trigger(event) 在一个 jQuery 对象的所有元素上触发指定事件的处理函数
triggerHandler(eventType) jQuery 对象的第一个元素上触发处理函数,而不执行默认操作或冒泡事件

清单 9-16 展示了如何手动触发事件处理程序。

清单 9-16 。手动触发事件处理程序

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

        $("img").bind({mouseenter: function() {
                $(this).css("border", "thick solid red");
            },
            mouseout: function() {
                $(this).css("border", "");
            }
        });

        $("<button>Trigger</button>").appendTo("#buttonDiv").bind("click", function (e) {
            $("#row1 img").trigger("mouseenter");
            e.preventDefault();
        });

    });

</script>
...

在这个脚本中,我使用bind方法在文档中的img元素上设置一对事件处理函数。然后,我使用appendTo方法将一个button元素插入到文档方法中,并使用bind方法为click事件注册一个处理函数。

当按钮被按下时,事件处理函数选择作为row1的后代的img元素,并使用trigger方法为mouseenter按钮调用它们的处理程序。效果如图 9-2 中的所示,就好像鼠标移动到了所有三个img元素上。

9781430263883_Fig09-02.jpg

图 9-2 。手动触发事件处理函数

使用事件对象

您还可以使用一个Event对象来触发其他元素的事件处理程序。这是一种在处理程序中使用的方便技术,如清单 9-17 所示。

清单 9-17 。用事件对象手动触发事件句柄

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

        $("#row1 img").bind("mouseenter", function() {
            $(this).css("border", "thick solid red");
        });

        $("#row2 img").bind("mouseenter", function(e) {
            $(this).css("border", "thick solid blue");
            $("#row1 img").trigger(e);
        });

    });

</script>
...

在清单 9-17 中,我使用bind方法给row1元素的img后代添加一个红色边框,以响应mouseenter事件。我对row2 img元素使用了蓝色边框,但是在处理程序中,我添加了以下语句:

..
$("#row1 img").trigger(e);
...

这种添加的效果是,当鼠标进入其中一个row2 img元素时,相同事件类型的处理程序也会在row1 img元素上被触发。你可以在图 9-3 中看到效果。

9781430263883_Fig09-03.jpg

图 9-3 。使用事件触发事件处理程序

当您希望触发当前正在处理的事件类型的处理程序时,这种方法很方便,但是您也可以通过指定事件类型来轻松获得相同的效果。

使用触发器处理程序方法

triggerHandler方法 调用处理函数,而不执行事件的默认动作,也不允许事件在 DOM 中冒泡。而且,与trigger方法不同,triggerHandler只在 jQuery 对象的第一个元素上调用处理函数。清单 9-18 展示了这种方法的使用。

清单 9-18 。使用触发器处理程序方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#row1 img").bind("mouseenter", function() {
            $(this).css("border", "thick solid red");
        });

        $("#row2 img").bind("mouseenter", function(e) {
            $(this).css("border", "thick solid blue");
            $("#row1 img").triggerHandler("mouseenter");
        });
    });
</script>
...

image 提示triggerHandler方法的结果是 handler 函数返回的结果,这意味着不能链接triggerHandler方法。

你可以在图 9-4 中看到这个脚本的效果。

9781430263883_Fig09-04.jpg

图 9-4 。使用触发器处理程序方法

使用事件速记方法

jQuery 定义了一些方便的方法,您可以用它们来为常用事件注册事件处理程序。在下面的表格中,我用一个function参数展示了这些速记方法。这是最常见的用法,相当于调用bind方法,但是这些方法需要更少的输入,并且(至少在我看来)使绑定到哪个事件变得更加明显。清单 9-19 展示了如何以这种方式使用速记方法。

清单 9-19 。使用事件速记方法绑定处理函数

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

        $("img").mouseenter(function() {
           $(this).css("border", "thick solid red");
        });

    });
</script>
...

这相当于将bind事件用于mouseenter事件,我已经在清单 9-20 中展示了这一点。

清单 9-20 。对 mouseenter 事件使用 bind 方法

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

        $("img").bind("mouseenter", function() {
           $(this).css("border", "thick solid red");
        });

    });
</script>
...

这一切都很好,到目前为止,您应该对这个示例的工作方式感到满意了。但是,您也可以使用速记方法来模拟trigger方法。通过调用不带参数的方法可以做到这一点。清单 9-21 展示了如何做到这一点。

清单 9-21 。使用事件速记方法触发事件处理程序

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

        $("img").bind("mouseenter", function() {
           $(this).css("border", "thick solid red");
        });

        $("<button>Trigger</button>").appendTo("#buttonDiv").click(function (e) {
            $("img").mouseenter();
            e.preventDefault();
        });
    });
</script>
...

我在文档中添加了一个button,单击它会选择img元素并调用它们的mouseenter事件处理程序。为了完整起见,清单 9-22 显示了使用trigger方法编写的等效功能。

清单 9-22 。使用触发方法

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

        $("img").bind("mouseenter", function() {
           $(this).css("border", "thick solid red");
        });

        $("<button>Trigger</button>").appendTo("#buttonDiv").click(function (e) {
            $("img").trigger("mouseenter");
            e.preventDefault();
        });
    });
</script>
...

在接下来的部分中,我列出了不同类别的速记方法和它们对应的事件。

使用文档事件速记方法

表 9-6 描述了适用于document对象的 jQuery 简写方法。

表 9-6 。文档事件速记方法

方法 描述
load(function) 对应于 load 事件,当文档中的元素和资源已经被加载时触发
ready(function) 当文档中的元素已经被处理并且 DOM 可以使用时触发
unload(function) 对应于 unload 事件,当用户离开页面时触发

ready法值得特别一提。它不直接对应于 DOM 事件,但是在使用 jQuery 时非常有用。你可以在第五章中看到使用ready方法的不同方式,在那里我解释了如何推迟脚本的执行直到 DOM 准备好,以及如何控制ready事件的执行。

使用浏览器事件速记方法

表 9-7 描述了浏览器事件,这些事件通常针对window对象(尽管errorscroll事件也用于元素)。

表 9-7 。浏览器事件速记方法

方法 描述
error(function) 对应于错误事件,当加载外部资源(如图像)出现问题时触发
resize(function) 对应于调整浏览器窗口大小时触发的 resize 事件
scroll(function) 对应于使用滚动条时触发的滚动事件

使用鼠标事件速记方法

表 9-8 描述了 jQuery 为处理鼠标事件而提供的一组速记方法。

表 9-8 。鼠标事件速记方法

方法 描述
click(function) 对应于用户按下并释放鼠标时触发的 click 事件
dblclick(function) 对应于 dblclick 事件,当用户快速连续按下并释放鼠标两次时触发
focusin(function) 对应于当元素获得焦点时触发的 focusin 事件
focusout(function) 对应于当元素失去焦点时触发的 focusout 事件
hover(function) hover(function, function) 当鼠标进入或离开一个元素时触发;当一个函数被指定时,它被用于进入和退出事件
mousedown(function) 对应于 mousedown 事件,当在元素上按下鼠标按钮时触发
mouseenter(function) 对应于 mouseenter 事件,当鼠标进入元素占据的屏幕区域时触发
mouseleave(function) 对应于 mouseleave 事件,当鼠标离开元素占据的屏幕区域时触发
mousemove(function) 对应于 mousemouse 事件,当鼠标在元素占据的屏幕区域内移动时触发
mouseout(function) 对应于 mouseout 事件,当鼠标离开元素占据的屏幕区域时触发
mouseover(function) 对应于 mouseover 事件,当鼠标进入元素占据的屏幕区域时触发
mouseup(function) 对应于 mouseup 事件,当在元素上按下鼠标按钮时触发

hover方法是一种将处理函数绑定到mouseentermouseleave事件的便捷方式。如果您提供两个函数作为参数,那么第一个函数被调用以响应mouseenter事件,第二个函数被调用以响应mouseleave。如果您只指定了一个函数,那么这两个事件都会调用它。清单 9-23 显示了hover方法的使用。

清单 9-23 。使用悬停方法

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

        $("img").hover(handleMouseEnter, handleMouseLeave);

        function handleMouseEnter(e) {
            $(this).css("border", "thick solid red");
        };

        function handleMouseLeave(e) {
            $(this).css("border", "");
        }
    });
</script>
...

使用表单事件速记方法

表 9-9 描述了 jQuery 提供的处理通常与表单相关的事件的速记方法。

表 9-9 。表单事件速记方法

方法 描述
blur(function) 对应于当元素失去焦点时触发的模糊事件
change(function) 对应于 change 事件,当元素的值改变时触发
focus(function) 对应于焦点事件,当元素获得焦点时触发
select(function) 对应于用户选择元素值时触发的选择事件
submit(function) 对应于提交事件,当用户提交表单时触发

使用键盘事件速记方法

表 9-10 描述了 jQuery 提供的处理键盘事件的速记方法。

表 9-10 。键盘事件速记方法

方法 描述
keydown(function) 对应于用户按键时触发的 keydown 事件
keypress(function) 对应于用户按下并释放按键时触发的按键事件
keyup(function) 对应于用户释放按键时触发的 keyup 事件

摘要

在本章中,我向您展示了 jQuery 对事件的支持。与大多数 jQuery 一样,事件功能的好处是简单和优雅。您可以轻松创建和管理事件处理程序。我特别喜欢对创建实时事件处理程序的支持,这样添加到 DOM 中与特定选择器匹配的元素就会自动与事件处理程序相关联。这大大减少了我在 web 应用中跟踪事件处理问题所花费的时间。在第十章中,我描述了 jQuery 对效果的支持。

十、使用 jQuery 效果

在很大程度上,jQuery UI 包含了与 jQuery 相关的用户界面(UI)功能,但是核心库包含了一些基本的效果和动画,这些是本章的主题。虽然我把它们描述为基本的,但是它们可以用来实现一些非常复杂的效果。主要的焦点是动画元素的可见性,但是您可以使用这些特性以多种方式动画一系列 CSS 属性。表 10-1 对本章进行了总结。

表 10-1 。章节总结

问题 解决办法 列表
显示或隐藏元素 使用显示隐藏的方法 one
切换元素的可见性 使用切换方法 2, 3
动画元素的可见性 显示隐藏切换方法提供一个 timespan 参数 four
在动画结束时调用函数 显示隐藏切换方法提供一个回调参数 5–7
沿垂直方向设置可见性动画 使用向下滑动向上滑动滑动切换的方法 eight
使用不透明度制作可见性动画 使用淡入淡出淡出切换淡出到的方法 9–11
创建自定义效果 使用动画方法 12–14
检查效果队列 使用队列方法 15, 16
停止并清除效果队列 使用停止结束的方法 17, 18
在效果队列中插入延迟 使用延迟方法 Nineteen
将自定义函数插入队列 使用带有函数参数的 queue 方法,并确保执行队列中的下一个函数 20, 21
禁用效果动画 $.fx.off 属性设置为 true Twenty-two

自上一版以来,JQUERY 发生了变化

jQuery 1.9/2.0 定义了一个新的finish方法,用于完成当前效果和清除事件队列。详见“停止效果和清除队列”一节。

使用基本效果

最基本的效果只是显示或隐藏元素。表 10-2 描述了你可以使用的方法。

表 10-2 。基本特效方法

方法 描述
hide() 隐藏 jQuery 对象中的所有元素
hide(time) hide(time, easing) 使用可选的缓动样式在指定的时间段内隐藏 jQuery 对象中的元素
hide(time, function) hide(time, easing, function) 使用可选的缓动样式和效果完成时调用的函数,在指定的时间段内隐藏 jQuery 对象中的元素
show() 显示了一个 jQuery 对象中的所有元素
show(time) show(time, easing) 使用可选的缓动样式显示指定时间段内 jQuery 对象中的元素
show(time, function) show(time, easing, function) 显示指定时间段内 jQuery 对象中的元素,带有可选的缓动样式和效果完成时调用的函数
toggle() 切换 jQuery 对象中元素的可见性
toggle(time) toggle(time, easing) 使用可选的缓动样式切换指定时间段内 jQuery 对象中元素的可见性
toggle(time, function) toggle(time, easing, function) 使用可选的缓动样式和效果完成时调用的函数,在指定时间段内切换 jQuery 对象中元素的可见性
toggle(boolean) 单向切换 jQuery 对象中的元素

清单 10-1 展示了这些效果中最简单的一个,那就是不带任何参数地使用showhide方法。

清单 10-1 。使用不带参数的 Show 和 Hide 方法

<!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"/>
    <script type="text/javascript">
        $(document).ready(function() {
            $("<button>Hide</button><button>Show</button>").appendTo("#buttonDiv")
                .click(function(e) {
                   if ($(e.target).text() == "Hide") {
                        $("#row1 div.dcell").hide();
                   } else {
                        $("#row1 div.dcell").show();
                   }
                   e.preventDefault();
                });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required />
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required />
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required />
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

我操纵 DOM(域对象模型)来添加两个button元素,并提供一个当它们中的任何一个被单击时被调用的函数。这个函数使用text方法来计算哪个button被使用了,并调用hideshow方法。在这两种情况下,我都使用选择器#row1 div.dcelljQuery对象上调用这个方法,这意味着那些dcell类中的div元素是具有row1id的元素的后代,它们将变得不可见或可见。图 10-1 显示了当我点击Hide按钮时会发生什么。

9781430263883_Fig10-01.jpg

图 10-1 。隐藏元素用 hide 元素

点击Show按钮调用show方法,恢复隐藏的元素,如图图 10-2 所示。

9781430263883_Fig10-02.jpg

图 10-2 。用 show 方法显示元素

很难用图来显示过渡,但是有几点需要注意。首先是过渡是即时的:没有延迟或影响,元素只是出现和消失。第二,在已经隐藏的元素上调用hide没有效果;在可见的元素上调用show也不行。最后,当您隐藏或显示一个元素时,您也可以显示或隐藏它的所有后代。

image 提示你可以使用:visible:hidden选择器来选择元素。有关 jQuery 扩展 CSS 选择器的详细信息,请参见第五章。

切换元素

您可以使用toggle方法将元素从可见或隐藏翻转回来。清单 10-2 给出了一个例子。

清单 10-2 。使用切换方法切换元素可见性

...
<script type="text/javascript">
    $(document).ready(function() {
        $("<button>Toggle</button>").appendTo("#buttonDiv")
            .click(function(e) {
                $("div.dcell:first-child").toggle();
                e.preventDefault();
            });
    });
</script>
...

在清单 10-2 中,我在文档中添加了一个按钮,当它被点击时,我使用toggle元素来改变div.dcell元素的可见性,这些元素是它们的父元素的第一个子元素。你可以在图 10-3 中看到效果。

9781430263883_Fig10-03.jpg

图 10-3 。切换元素的可见性

image 提示注意,文档的结构在隐藏的元素周围折叠。如果您想隐藏元素并在屏幕上留出空间,那么您可以将 CSS visibility属性设置为hidden

向一个方向切换

您可以向toggle方法传递一个boolean参数来限制可见性切换的方式。如果您将true作为参数传递,那么将只显示隐藏的元素(可见的元素不会被隐藏)。如果您将false作为参数传递,那么您会得到相反的效果。可见元素将被隐藏,但隐藏的元素将不可见。清单 10-3 显示了这种风格的toggle方法的使用。清单 10-3 中的脚本创建了如图 10-3 所示的效果。

清单 10-3 。在一个方向上使用切换方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("<button>Toggle</button>").appendTo("#buttonDiv")
            .click(function(e) {
                $("div.dcell:first-child").toggle(false);
                e.preventDefault();
            });
    });
</script>
...

制作元素可见性的动画

您可以通过向showhidetoggle方法传递一个时间跨度来动画显示和隐藏元素的过程。然后,在指定的时间段内,逐渐执行显示和隐藏元素的过程。表 10-3 显示了你可以使用的不同时间跨度参数。

表 10-3 。时间跨度参数

方法 描述
<number> 规定的
slow 相当于 600 毫秒的简写
fast 相当于 200 毫秒的速记

清单 10-4 展示了如何动画显示和隐藏元素。

清单 10-4 。动画元素的可见性

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

        $("<button>Toggle</button>").appendTo("#buttonDiv")
            .click(function(e) {
                $("img").toggle("fast", "linear");
                e.preventDefault();
            });

    });
</script>
...

在清单 10-4 中,我使用了fast值来指定切换文档中img元素的可见性应该在 600 毫秒内完成。

image 提示当指定以毫秒为单位的持续时间时,确保该值没有被引用。也就是用$("img").toggle(500)而不用$("img").toggle("500")。如果您使用引号,那么该值将被忽略。

我还提供了一个额外的参数,它指定了动画的样式,称为缓动样式缓动函数。有两种缓动样式可用,swinglinear。使用swing风格制作动画时,动画开始缓慢,加速,然后在动画结束时再次减速。linear风格在整个动画中保持不变的节奏。如果省略参数,则使用swing。你可以看到动画隐藏图 10-4 中元素的效果。用这种方式展示动画很难,但是你会感觉到发生了什么。

9781430263883_Fig10-04.jpg

图 10-4 。动画隐藏元素

如图所示,动画效果在两个维度上缩小了图像的大小,并降低了不透明度。动画结束时,img元素不可见。图 10-5 显示了如果再次点击Toggle按钮使img元素可见会发生什么。

9781430263883_Fig10-05.jpg

图 10-5 。动画显示元素

使用效果回调

您可以提供一个函数作为showhidetoggle方法的参数,当这些方法完成它们的效果时,这个函数将被调用。这对于更新其他元素以反映状态的变化很有用,如清单 10-5 所示。

清单 10-5 。使用事件回调

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

        var hiddenRow = "#row2";
        var visibleRow = "#row1";

        $(hiddenRow).hide();

        function switchRowVariables() {
            var temp = hiddenRow;
            hiddenRow = visibleRow;
            visibleRow = temp;
        }

        function hideVisibleElement() {
            $(visibleRow).hide("fast", showHiddenElement);
        }

        function showHiddenElement() {
            $(hiddenRow).show("fast", switchRowVariables);
        }

        $("<button>Switch</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                hideVisibleElement();
                e.preventDefault();
            });
    });
</script>
...

image 提示如果你想在一个元素上执行多个连续效果,那么你可以使用常规的 jQuery 方法链接。有关详细信息,请参见“创建和管理效果队列”一节。

为了让清单 10-5 更清晰,我将效果活动分解成了独立的函数。为了进行设置,我隐藏了一个在 CSS 表格布局中充当一行的div元素,并定义了两个变量来跟踪哪一行可见,哪一行不可见。我在文档中添加了一个button元素,当这个元素被点击时,我调用hideVisibleElement函数,它使用hide方法来显示隐藏可见行的动画。

...
$(visibleRow).hide("fast",showHiddenElement);
...

当效果完成时,我指定我想要执行的功能的名称,在这个例子中是showHiddenElement

image 提示回调函数没有传递任何参数,但是this变量被设置为被动画化的 DOM 元素。如果多个元素被动画化,那么回调函数将为每个元素调用一次。

此函数使用 show 方法显示元素的动画,如下所示:

...
$(hiddenRow).show("fast",switchRowVariables);
...

我再次指定了一个在效果结束时执行的函数。在这种情况下,是switchRowVariables函数,它打乱了跟踪可见性的变量,以便您在下次单击button时对正确的元素执行效果。结果是,当单击按钮时,当前行被隐藏的行替换,并有一个快速的动画来减少过渡对用户的干扰。图 10-6 显示了这个效果(尽管,同样,只有当你在浏览器中加载这个例子时,真正的效果才变得明显)。

9781430263883_Fig10-06.jpg

图 10-6 。使用回调函数链接效果

你通常不需要像我这样分解单个函数,所以清单 10-6 显示了使用一组更简洁的内联函数重写的相同例子。

清单 10-6 。使用内联回调函数

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

        var hiddenRow = "#row2";
        var visibleRow = "#row1";

        $(hiddenRow).hide();

        $("<button>Switch</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                $(visibleRow).hide("fast", function() {
                    $(hiddenRow).show("fast", function() {
                        var temp = hiddenRow;
                        hiddenRow = visibleRow;
                        visibleRow = temp;
                    });
                });

                e.preventDefault();
            });
    });
</script>
...

创建循环效果

您可以使用回调函数产生在循环中执行的效果。清单 10-7 展示了。

清单 10-7 。使用回调函数创建循环效果

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

        $("<button>Toggle</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                performEffect();
                e.preventDefault();
            });

        function performEffect() {
            $("h1").toggle("slow", performEffect)
        }
    });
</script>
...

在本例中,点击按钮导致执行performEffect功能。这个函数使用toggle方法来改变文档中h1元素的可见性,并将自己作为回调参数传递。结果是h1元素在可见和隐藏之间循环,如图图 10-7 所示。

9781430263883_Fig10-07.jpg

图 10-7 。在循环中执行效果

image 提示当使用当前函数作为回调函数时需要小心。最终,您将耗尽 JavaScript 调用堆栈,您的脚本将停止工作。解决这个问题最简单的方法是使用setTimeout函数,它将调度对目标函数的回调,而不嵌套函数调用,就像这样:$("h1").toggle("slow", setTimeout( performEffect, 1))。耗尽调用堆栈实际上是相当困难的,这通常意味着让动画页面运行很长时间,但这一点值得记住——然而,在使用finish方法时需要小心,我在“停止效果和清除队列”一节中对此进行了描述

使用效果负责

在我看来,像这样的循环应该谨慎使用,并且只在它们服务于某个目的时使用(我指的是为用户服务的目的,而不是炫耀您出色的 jQuery 效果技能)。一般来说,任何一种效应的影响都应该仔细考虑。它在开发过程中可能看起来很棒,但是不明智地使用效果会破坏用户对 web 应用的享受,特别是如果这是一个他或她每天都在使用的应用。

举个简单的例子,我是一个热衷跑步的人(热衷但没有任何好处)。我曾经有一个跑步者的手表,它收集关于我的心率、速度、距离、燃烧的卡路里和 100 个其他数据点的数据。在运行结束时,我会将数据上传到制造商的网站进行存储和分析。

这是疼痛开始的地方。每当我点击页面上的一个按钮,我想要的内容就会通过一个长长的效果显示出来。我知道浏览器已经收到了我想要的数据,因为我可以看到它被逐渐显示出来,但在我可以读取它之前还有几秒钟。几秒钟听起来可能不是很多,但确实是很多,尤其是当我想随时查看五到十个不同的数据项时。

我确信应用的设计者认为效果很好,并且增强了体验。但是他们没有。它们太糟糕了,以至于使用该应用成为一种令人咬牙切齿的体验——以至于从这本书的上一版开始,我就购买了竞争对手的产品。

这款手表(现已废弃)的网络应用有一些有用的数据分析工具,但它让我如此恼火,以至于我愿意支付数百美元来更换。如果没有这些影响(也许还有我发现自己以惊人的频率消费的啤酒和披萨),我现在可能是马拉松冠军。

如果你认为我夸大了效果。。。你可以相信我关于比萨饼的话),然后选择本章中的一个列表,将时间跨度设置为两秒。然后感受一下你等待效果完成的时间有多长。

我的建议是,所有的效果都应该少用。我倾向于只在对 DOM 进行不和谐的更改时使用它们(元素突然从页面中消失)。当我使用它们时,我保持时间跨度很短,通常是 200 毫秒。我从不使用无限循环。这只是让用户头疼的一个原因。我敦促你花时间去思考你的用户是如何参与到你的应用或网站中的,并去掉那些不会让手头的任务更容易执行的东西。有光泽的网站是好的,但有用的有光泽的网站是伟大的

使用幻灯片效果

jQuery 有一组在屏幕上滑动元素的效果。表 10-4 描述了这种方法。

表 10-4 。幻灯片效果方法

方法 描述
slideDown()``slideDown((time, function) 通过向下滑动来显示元素
slideUp()``slideUp(time, function) 通过向上滑动来隐藏元素
slideToggle()``slideToggle(time, function) 通过上下滑动来切换元素的可见性

这些方法使垂直轴上的元素具有动画效果。这些方法的论据是关于基本效果的。您可以选择提供一个时间跨度、一种放松方式和一个回调函数。清单 10-8 显示了使用中的幻灯片效果。

清单 10-8 。使用幻灯片效果

...
<script type="text/javascript">
    $(document).ready(function() {
        $("<button>Toggle</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                $("h1").slideToggle("fast");
                e.preventDefault();
            });
    });
</script>
...

在这个脚本中,我使用slideToggle方法来切换h1元素的可见性。你可以在图 10-8 中看到效果。

9781430263883_Fig10-08.jpg

图 10-8 。使用幻灯片效果显示元素

该图显示了可见的h1元素。元素被剪裁,而不是缩放,因为 jQuery 通过操纵元素的高度来创建效果。你可以在图 10-9 中看到我的意思。

9781430263883_Fig10-09.jpg

图 10-9 。jQuery 通过操纵元素的高度来创建效果

该图显示了h1元素可见时的特写。您可以看到文本的大小没有改变,只是显示的数量有所变化。然而,这对于图像来说是不正确的,因为浏览器会自动缩放它们。如果你仔细看,你可以看到整个背景图像总是显示,但它被缩小以适应高度。

使用渐变效果

渐变效果方法通过降低元素的不透明度(或者,如果您喜欢,增加其透明度)来显示和隐藏元素。表 10-5 描述了渐变效果的方法。

表 10-5 。渐变效果方法

方法 描述
fadeOut()``fadeOut(timespan)``fadeOut(timespan, function) 通过降低不透明度来隐藏元素
fadeIn()``fadeIn(timespan)``fadeIn(timespan, function) 通过增加不透明度来显示元素
fadeTo(timespan, opacity) fadeTo(timespan, opacity, easing, function) 将不透明度更改为指定的级别
fadeToggle()``fadeToggle(timespan)``fadeToggle(timespan, function) 使用不透明度切换元素的可见性

fadeOutfadeInfadeToggle方法与其他效果方法一致。您可以提供时间跨度、放松方式和回调函数,就像前面的清单一样。清单 10-9 演示了如何使用淡入淡出。

清单 10-9 。通过淡化显示和隐藏元素

...
<script type="text/javascript">
    $(document).ready(function() {
        $("<button>Toggle</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                $("img").fadeToggle();
                e.preventDefault();
            });
    });
</script>
...

我将fadeToggle方法应用于文档中的img元素,部分是为了演示这种效果的局限性之一。图 10-10 显示了当你隐藏元素时会发生什么。

9781430263883_Fig10-10.jpg

图 10-10 。使用渐变效果

淡入淡出效果只对不透明度起作用,不像其他效果那样也会改变所选元素的大小。这意味着在元素完全透明之前,您会得到一个很好的平滑淡入淡出效果,此时 jQuery 会隐藏它们,并且页面会捕捉到新的布局。如果不小心使用,这最后一个阶段可能会有些不和谐。

渐隐到特定的不透明度

您可以使用fadeTo方法将元素渐变到特定的不透明度。不透明度值的范围是在0(完全透明)到1(完全不透明)之间的数字。元素的可见性没有改变,所以避免了我提到的页面布局的快照。清单 10-10 展示了fadeTo方法的使用。

清单 10-10 。渐隐到特定的不透明度

...
<script type="text/javascript">
    $(document).ready(function() {
        $("<button>Fade</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                $("img").fadeTo("fast", 0);
                e.preventDefault();
            });
    });
</script>
...

在这个例子中,我已经指定了img元素应该被淡化,直到它们完全透明。这与fadeOut方法有相同的效果,但是不会在过渡结束时隐藏元素。图 10-11 显示效果。

9781430263883_Fig10-11.jpg

图 10-11 。用 fadeTo 方法淡出元素

你不必将元素淡化到不透明度范围的极端。你也可以指定中间值,如清单 10-11 所示。

清单 10-11 。渐隐到特定的不透明度

...
<script type="text/javascript">
    $(document).ready(function() {
        $("<button>Fade</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                $("img").fadeTo("fast", 0.4);
                e.preventDefault();
            });
    });
</script>
...

你可以在图 10-12 中看到效果。

9781430263883_Fig10-12.jpg

图 10-12 。渐隐到特定的不透明度

创建自定义效果

jQuery 不会将您局限于基本的滑动和渐变效果。你也可以创造你自己的。表 10-6 显示了你在这个过程中使用的方法。

表 10-6 。自定义特效方法

方法 描述
animate(properties)``animate(properties, time)``animate(properties, time, function) 动画显示一个或多个 CSS 属性,具有可选的时间跨度、缓动样式和回调函数
animate(properties, options) 动画显示一个或多个 CSS 属性,将选项指定为贴图

jQuery 可以动态显示任何接受简单数值的属性(例如,height属性)。

image 注意能够动画显示数字 CSS 属性意味着你不能动画显示颜色。有几种方法可以解决这个问题。第一个(也是我认为最好的)解决方案是使用 jQuery UI,我在本书的第四部分对此进行了描述。如果您不想使用 jQuery UI,那么您可能会考虑使用原生浏览器对 CSS 动画的支持。这些浏览器的性能相当好,但是目前的支持是不完整的,并且在旧版本的浏览器中不存在。关于 CSS 动画的细节,请参见我的书《HTML5 的权威指南》,这本书也是由 Apress 出版的。我最不喜欢的方法是使用 jQuery 插件。制作颜色动画很难,我还没有找到一个我完全满意的插件,但是我找到的最可靠的插件是从https://github.com/jquery/jquery-color获得的。

您可以提供一组要作为地图对象制作动画的属性,如果需要,您可以对要设置的选项进行同样的操作。清单 10-12 显示了一个自定义动画。

清单 10-12 。使用自定义动画

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

        $("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
        $("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});

        $("<button>Animate</button>").insertAfter("#buttonDiv button")
            .click(function(e) {

                $("h1").animate({
                    height: $("h1").height() + $("form").height() + 10,
                    width: ($("form").width())
                });

                e.preventDefault();
            });
    });
</script>
...

在清单 10-12 的中,我想要动画显示h1元素的尺寸,这样它的背景图像就会延伸到form元素的后面。在我这样做之前,我需要对受影响的元素的 CSS(层叠样式表)进行一些更改。我可以使用我在第三章中定义的样式表来做这件事,但是因为这是一本关于 jQuery 的书,所以我选择使用 JavaScript。为了使动画更容易管理,我使用fixed模式定位了formh1元素,并使用了z-index属性来确保h1元素显示在form的下方。

我在文档中添加了一个button,当单击它时调用animate方法。我选择使用通过其他 jQuery 方法获得的信息来制作heightwidth属性的动画。你可以在图 10-13 中看到动画的效果。

9781430263883_Fig10-13.jpg

图 10-13 。执行自定义动画

我在图中只显示了开始和结束状态,但是 jQuery 提供了平滑的过渡,就像其他效果一样,您可以通过指定时间跨度和放松样式来控制过渡。

使用绝对目标属性值

请注意,您只为动画指定了最终值。jQuery 自定义动画的起点是正在制作动画的属性的当前值。我使用了从其他 jQuery 方法中获得的值,但是您还有其他选择。首先,也是最明显的,你可以使用绝对值,如清单 10-13 所示。

清单 10-13 。使用绝对值执行自定义动画

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

        $("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
        $("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});

        $("<button>Animate</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                $("h1").animate({
                    left: 50,
                    height: $("h1").height() + $("form").height() + 10,
                    width: ($("form").width())
                });

                e.preventDefault();
            });
    });
</script>
...

在清单 10-13 的中,我给动画添加了left属性,指定了50的绝对值(将被视为 50 像素)。这将把h1元素向右移动。图 10-14 显示了动画的结果。

9781430263883_Fig10-14.jpg

图 10-14 。使用固定的最终属性值创建自定义动画

使用相对目标属性值

也可以使用相对值指定动画目标。您可以通过在值前加上前缀+=来指定增加,并在值前加上前缀-=来指定减少。清单 10-14 展示了相对值的使用。

清单 10-14 。在自定义动画效果中使用相对值

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

        $("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
        $("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});

        $("<button>Animate</button>").insertAfter("#buttonDiv button")
            .click(function(e) {
                $("h1").animate({
                    height: "+=100",
                    width: "-=700"
                });

                e.preventDefault();
            });
    });
</script>
...

创建和管理效果队列

当您使用效果时,jQuery 会创建一个它必须执行的动画队列,并以自己的方式处理它们。有一组方法可以用来获取队列信息或控制队列,如表 10-7 中所述。

表 10-7 。效果队列方法

方法 描述
queue() 返回将在 jQuery 对象中的元素上执行的效果队列
queue(function) 将函数添加到队列的末尾
dequeue() 删除并执行队列中的第一个项目,该项目对应于 jQuery 对象中的元素
stop()``stop(clear) 停止当前动画
finish() 停止当前动画并清除任何排队的动画
delay(time) 在队列中的效果之间插入延迟

你可以通过链接对效果相关方法的调用来创建一个效果队列,如清单 10-15 所示。

清单 10-15 。创建效果队列

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

        $("form").css({"position": "fixed", "top": "70px", "z-index": "2"});
        $("h1").css({"position": "fixed", "z-index": "1", "min-width": "0"});

        var timespan = "slow";

        cycleEffects();

        function cycleEffects() {
                $("h1")
                .animate({left: "+=100"}, timespan)
                .animate({left: "-=100"}, timespan)
                .animate({height: 223,width: 700}, timespan)
                .animate({height: 30,width: 500}, timespan)
                .slideUp(timespan)
                .slideDown(timespan, cycleEffects);
        }
    });
</script>
...

清单 10-15 中的脚本使用常规的 jQuery 方法链接将h1元素上的一系列效果串在一起。最后一个效果使用cycleEffects函数 作为回调,再次开始这个过程。这是一个相当恼人的序列。它有一会儿催眠作用,然后有一点刺激,然后就变成了引起头痛的那种效果。但是它确实创建了一个效果队列,这是我演示队列特性所需要的。

image 注意我本可以使用回调函数来达到同样的效果,但是这并没有创建效果队列,因为启动下一个动画的函数直到前一个动画完成后才被执行。当您使用常规方法链接时,如本例所示,所有的方法都被计算,动画效果被放入队列中。使用方法链接的限制是您只能使用当前选择。使用回调时,您可以将涉及完全不相关元素的序列串在一起。

显示效果队列中的项目

您可以使用queue方法来检查效果队列的内容。这并不完全有帮助,因为队列包含两种类型的数据对象之一。如果一个效果正在被执行,那么队列中相应的项就是字符串值inprogress。如果效果没有被执行,队列中的项是将被调用的函数(尽管该函数没有透露将执行哪个动画函数)。也就是说,检查内容是从队列开始的好地方,清单 10-16 展示了如何做到这一点。

清单 10-16 。检查效果队列的内容

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

        $("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
        $("form").remove();
        $("<table border=1></table>")
            .appendTo("body").css({
                position: "fixed", "z-index": "2",
                "border-collapse": "collapse", top: 100
            });

        var timespan = "slow";

        cycleEffects();
        printQueue();

        function cycleEffects() {
            $("h1")
            .animate({ left: "+=100" }, timespan)
            .animate({ left: "-=100" }, timespan)
            .animate({ height: 223, width: 700 }, timespan)
            .animate({ height: 30, width: 500 }, timespan)
            .slideUp(timespan)
            .slideDown(timespan, cycleEffects);
        }

        function printQueue() {
            var q = $("h1").queue();
            var qtable = $("table");
            qtable.html("<tr><th>Queue Length:</th><td>" + q.length + "</td></tr>");
            for (var i = 0; i < q.length; i++) {
                var baseString = "<tr><th>" + i + ":</th><td>";
                if (q[i] == "inprogress") {
                    $("table").append(baseString + "In Progress</td></tr>");
                } else {
                    $("table").append(baseString + q[i] + "</td></tr>");
                }
            }
            setTimeout(printQueue, 500);
        }
    });
</script>
...

在这个例子中我不需要form元素,所以我已经将它从 DOM 中移除,并用一个简单的table来替换它,我将用它来显示效果队列的内容。我添加了一个名为printQueue的重复函数,该函数调用queue方法,并在table中显示项目的数量和每个项目的一些细节。正如我所说的,队列中的项目本身并不是特别有用,但是它们确实让您对正在发生的事情有了一个总体的了解。图 10-15 显示了 jQuery 如何在效果队列中前进。

9781430263883_Fig10-15.jpg

图 10-15 。检查队列的内容

清单 10-16 很难用静态图像来描绘。我建议您将文档加载到浏览器中自己查看。当第一次调用cycleEffects函数时,效果队列中有六个项目,其中第一个显示为正在进行中。其他的是管理动画的匿名函数的实例。每个效果完成后,jQuery 会从队列中删除该项目。最后一个效果结束时,再次调用cycleEffects函数,再次将 6 个项目放入队列。

停止效果并清除队列

您可以使用stopfinish方法来中断 jQuery 当前正在执行的效果。对于stop方法,您可以为此方法提供两个可选参数,这两个参数都是boolean值。如果您将true作为第一个参数传递,那么所有其他效果都将从队列中移除,并且不会被执行。如果您将true作为第二个参数传递,那么被当前动画激活的 CSS 属性将立即被设置为它们的最终值。

两个参数的缺省值都是false,这意味着只有当前的效果从队列中移除,而正在被动画化的属性保持在效果被中断时的设置值。如果不清除队列,jQuery 将继续处理下一个效果,并开始正常执行。清单 10-17 提供了一个使用stop方法的例子。

清单 10-17 。使用停止方法

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

        $("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
        $("form").remove();

        $("<table border=1></table>")
            .appendTo("body").css({
                position: "fixed", "z-index": "2",
                "border-collapse": "collapse", top: 100
            });

        $("<button>Stop</button><button>Start</button>")
          .appendTo($("<div/>").appendTo("body")
            .css({
                position: "fixed", "z-index": "2",
                "border-collapse": "collapse", top: 100, left: 200
            })).click(function (e) {
                $(this).text() == "Stop" ? $("h1").stop(true, true) : cycleEffects();
            });

        var timespan = "slow";

        cycleEffects();
        printQueue();

        function cycleEffects() {
            $("h1")
            .animate({ left: "+=100" }, timespan)
            .animate({ left: "-=100" }, timespan)
            .animate({ height: 223, width: 700 }, timespan)
            .animate({ height: 30, width: 500 }, timespan)
            .slideUp(timespan)
            .slideDown(timespan, cycleEffects);
        }

        function printQueue() {
            var q = $("h1").queue();
            var qtable = $("table");
            qtable.html("<tr><th>Queue Length:</th><td>" + q.length + "</td></tr>");

            for (var i = 0; i < q.length; i++) {
                var baseString = "<tr><th>" + i + ":</th><td>";
                if (q[i] == "inprogress") {
                    $("table").append(baseString + "In Progress</td></tr>");
                } else {
                    $("table").append(baseString + q[i] + "</td></tr>");
                }
            }
            setTimeout(printQueue, 500);
        }
    });
</script>
...

image 提示当你调用stop方法时,任何与当前效果相关的回调都不会被执行。当您使用stop方法清除队列时,不会执行与队列中任何效果相关联的回调。

为了演示stop方法,我在文档中添加了两个按钮。当点击Stop按钮时,我调用stop方法,传入两个true参数。这样做的效果是清除效果队列的其余部分,并将元素与正在被动画化的属性的目标值对齐。由于使用stop方法时没有调用回调函数,因此cycleEffects方法调用的循环被中断,动画暂停。当点击Start按钮时,调用cycleEffects方法,动画恢复。

image 提示在动画运行时点击Start按钮不会混淆 jQuery。它只是将由cycleEffects方法使用的效果添加到效果队列中。回调的使用意味着队列的大小会有一点跳跃,但是就动画而言,一切都照常进行。

finish方法 具有与调用stop(true, true)相似的效果,但是在处理被动画化的 CSS 属性的方式上有所不同。当调用stop(true, true)时,被当前效果激活的 CSS 属性跳转到它们的最终值,但是当使用finish方法时,被当前效果激活的 CSS 属性和所有排队的效果跳转到它们的最终值。你可以在清单 10-18 中看到我是如何应用finish方法的。

清单 10-18 。使用完成方法

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

        $("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
        $("form").remove();

        $("<table border=1></table>")
            .appendTo("body").css({
                position: "fixed", "z-index": "2",
                "border-collapse": "collapse", top: 100
            });

        var finishAnimations = false;

        $("<button>Stop</button><button>Start</button>")
          .appendTo($("<div/>").appendTo("body")
            .css({
                position: "fixed", "z-index": "2",
                "border-collapse": "collapse", top: 100, left: 200
            })).click(function (e) {
                if ($(this).text() == "Stop") {
                    finishAnimations = true;
                    $("h1").finish();
                } else {
                    finishAnimations = false;
                    cycleEffects();
                }
            });

        var timespan = "slow";

        cycleEffects();
        printQueue();

        function cycleEffects() {
            $("h1")
            .animate({ left: "+=100" }, timespan)
            .animate({ left: "-=100" }, timespan)
            .animate({ height: 223, width: 700 }, timespan)
            .animate({ height: 30, width: 500 }, timespan)
            .slideUp(timespan)
            .slideDown(timespan, function () {
                if (!finishAnimations) {
                    cycleEffects();
                }
            });
        }

        function printQueue() {
            var q = $("h1").queue();
            var qtable = $("table");
            qtable.html("<tr><th>Queue Length:</th><td>" + q.length + "</td></tr>");

            for (var i = 0; i < q.length; i++) {
                var baseString = "<tr><th>" + i + ":</th><td>";
                if (q[i] == "inprogress") {
                    $("table").append(baseString + "In Progress</td></tr>");
                } else {
                    $("table").append(baseString + q[i] + "</td></tr>");
                }
            }
            setTimeout(printQueue, 500);
        }
    });
</script>
...

当使用finish方法时,必须小心效果循环,比如清单 10-18 中的那个。为了确定所有动画属性的最终 CSS 值,finish方法需要执行所有的效果——尽管没有任何时间延迟——这意味着任何回调函数也要执行。

在清单 10-18 中,cycleEffects函数中定义的最后一个效果设置下一个效果循环,如下所示:

...
.slideDown(timespan, cycleEffects);
...

finish方法不会阻止新的效果被添加到队列中,并且当它被调用时,它不会跟踪队列的状态。这意味着finish方法将调用cycleEffects方法,该方法将效果添加到队列中,然后finish方法执行该方法,触发回调,该回调将效果添加到队列中。。。等等。总体效果是立即耗尽 JavaScript 调用堆栈。

为了避免这种情况,我添加了一个名为finishAnimations的变量,我设置这个变量来响应被点击的button元素,并在将下一组效果添加到队列之前进行检查,如下所示:

...
.slideDown(timespan, function () {
    if (!finishAnimations) {
        cycleEffects();
    }
});
...

在队列中插入延迟

您可以使用delay方法 在队列中的两个效果之间引入暂停。此方法的参数是延迟应该持续的毫秒数。清单 10-19 展示了使用这种方法在效果队列中引入一秒钟的延迟。

清单 10-19 。使用延迟方法

...
function cycleEffects() {
    $("h1")
    .animate({ left: "+=100" }, timespan)
    .animate({ left: "-=100" }, timespan)
    .delay(1000)
    .animate({ height: 223, width: 700 }, timespan)
    .animate({ height: 30, width: 500 }, timespan)
    .delay(1000)
    .slideUp(timespan)
    .slideDown(timespan, function () {
        if (!finishAnimations) {
            cycleEffects();
        }
    });
}
...

将函数插入队列

您可以使用queue方法将自己的函数添加到队列中,它们将像标准效果方法一样被执行。您可以使用此功能启动其他动画,根据外部变量优雅地退出动画链,或者做任何您需要的事情。清单 10-20 包含了一个例子。

清单 10-20 。将自定义函数插入队列

...
function cycleEffects() {
    $("h1")
    .animate({ left: "+=100" }, timespan)
    .animate({ left: "-=100" }, timespan)
    .queue(function () {
        $("body").fadeTo(timespan, 0).fadeTo(timespan, 1);
        $(this).dequeue();
    })
    .delay(1000)
    .animate({ height: 223, width: 700 }, timespan)
    .animate({ height: 30, width: 500 }, timespan)
    .delay(1000)
    .slideUp(timespan)
    .slideDown(timespan, function () {
        if (!finishAnimations) {
            cycleEffects();
        }
    });
}
...

this变量被设置为调用该方法的jQuery对象。这很有用,因为你必须确保在函数中的某个点调用dequeue方法,以便将队列移动到下一个效果或函数。在这个例子中,我使用了queue方法来添加一个函数,该函数将body元素渐变为完全透明,然后返回。

image 提示我在自定义函数中添加的效果被添加到body元素的效果队列中。每个元素都有自己的队列,您可以独立地管理它们。如果您想在您正在操作的队列的相同元素上制作多个属性的动画,那么您只需使用animate方法。否则,你的效果将会按顺序添加到队列中。

或者,您可以接受该函数的单个参数,它是队列中的下一个函数。在这种情况下,你必须调用函数将队列移动到下一个效果,如清单 10-21 所示。

清单 10-21 。使用传递给自定义函数的参数

...
function cycleEffects() {
    $("h1")
    .animate({ left: "+=100" }, timespan)
    .animate({ left: "-=100" }, timespan)
    .queue(function (nextFunction) {
        $("body").fadeTo(timespan, 0).fadeTo(timespan, 1);
        nextFunction();
    })
    .delay(1000)
    .animate({ height: 223, width: 700 }, timespan)
    .animate({ height: 30, width: 500 }, timespan)
    .delay(1000)
    .slideUp(timespan)
    .slideDown(timespan, function () {
        if (!finishAnimations) {
            cycleEffects();
        }
    });
}
...

image 注意如果你不调用下一个函数或者调用dequeue方法,效果序列将会停止。

启用和禁用效果动画

您可以通过将$.fx.off属性的值设置为true来禁用效果的动画,如清单 10-22 所示。

清单 10-22 。禁用动画

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

        $.fx.off = true;

        $("h1").css({ "position": "fixed", "z-index": "1", "min-width": "0" });
        $("form").remove();

        // ...
*statements omitted for brevity*...

    });
</script>
...

当动画被禁用时,对 effect 方法的调用会导致元素立即与它们的目标属性值对齐。时间跨度被忽略,并且没有中间动画。当动画被禁用时,循环效果集将很快达到调用堆栈限制。为了避免这种情况,使用setTimeout方法,如本章前面所述。

摘要

在本章中,我向您展示了如何使用 jQuery effect 特性。内置的效果方法主要是让元素以不同的方式可见和不可见,但您可以超越这一点,将任何数字 CSS 属性动画化。您还可以深入效果队列,对应用于元素的效果序列进行更多控制。在第十一章的中,我重构了示例文档,向您展示如何将本章和前面章节中涉及的一些基本 jQuery 特性结合起来。

十一、重构示例:第一部分

在前面的章节中,我单独展示了每个功能区域:如何处理事件,如何操作 DOM(域对象模型)等等。当您组合这些特性时,jQuery 的真正威力和灵活性就会显现出来,在这一章中,我将通过重构花店示例文档来演示这种组合。

我在这一章中所做的所有修改都在script元素中。我没有改变示例文档的底层 HTML。与大多数 jQuery 特性一样,有许多不同的途径可以达到相同的结果。我在本章中采用的方法反映了我最喜欢的 jQuery 部分以及我倾向于考虑 DOM 的方式。你可能有不同的思维模式,喜欢不同的方法组合。这真的无关紧要,而且没有唯一正确的 jQuery 使用方法。

查看示例文档

我从一个简单的示例文档开始写这本书,一个基本的花店页面。在接下来的章节中,我使用 jQuery 从文档中选择元素,探索并重新排列它的 DOM,监听事件,并对元素应用效果。在我开始重构这个例子之前,让我们回顾一下我开始的地方。清单 11-1 显示了基本文档。

清单 11-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"/>
    <script type="text/javascript">
        $(document).ready(function() {
             // jQuery statements will go here
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required />
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required />
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required />
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

我强调了script元素,因为这是你在本书中花费时间的地方。我为ready事件添加了无处不在的 jQuery 处理程序,但仅此而已。没有其他 JavaScript 语句。你可以在图 11-1 中看到未经修饰的文档是如何出现在浏览器中的。

9781430263883_Fig11-01.jpg

图 11-1 。基本示例文档

添加额外的花卉产品

我做的第一个改变是给商店增加一些额外的花。我想这样做是为了演示如何在一个循环中创建元素。清单 11-2 显示了添加的script元素。

清单 11-2 。向页面添加产品

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

我已经定义了另外三种类型的花(CarnationLilyOrchid),并创建了一个新的div元素,它被分配给了drow类,我将它附加到现有的div元素上,该元素在 CSS(级联样式表)表格布局模型中充当表格。

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

我使用filterend方法来缩小和扩大选择范围,使用attr方法来设置属性值。最后,我为每朵新的花填充了一组完整的元素,插入到行级的div元素中,然后再插入到表级的元素中。你可以在图 11-2 中看到效果。

9781430263883_Fig11-02.jpg

图 11-2 。向页面添加新的花朵

本例中一个很好的 jQuery 特性是,您可以选择和导航没有附加到主文档的元素。当我克隆模板元素时,它们不是文档的一部分,但是我仍然可以使用childrenfilter方法来缩小选择范围。

添加旋转按钮

我将创建一个简单的旋转木马,让用户通过花集页面。首先,我需要分页的左右按钮。清单 11-3 显示了我如何将它们添加到文档中。

清单 11-3 。添加转盘按钮

...
<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")
            .css({
                "background-image": "url(leftarrows.png)",
                "float": "left",
                "margin-top": "15px",
                display: "block", width: 50, height: 50
            }).click(handleArrowPress).hover(handleArrowMouse)

        $("#right").css("background-image", "url(rightarrows.png)").appendTo("form");

        $("#oblock").css({float: "left", display: "inline", border: "thin black solid"});
        $("form").css({"margin-left": "auto", "margin-right": "auto", width: 885});

        function handleArrowMouse(e) {
        }

        function handleArrowPress(e) {
        }
    });
</script>
...

我定义了一对a元素,将它们添加到form元素的前面,并使用css方法为许多不同的属性应用值。

...
$("<a id=left></a><a id=right></a>").prependTo("form")
    .css({
        "background-image": "url(leftarrows.png)",
        "float": "left",
        "margin-top": "15px",
        display: "block",width: 50, height: 50
}).click(handleArrowPress).hover(handleArrowMouse)
...

关键属性是background-image,我设置为leftarrows.png。你可以在图 11-3 中看到这个图像。

9781430263883_Fig11-03.jpg

图 11-3 。leftarrows.png 形象

该图像在组合图像中包含三个不同的箭头。每个单独的箭头都是 50 像素宽,通过将widthheight属性设置为50,我确保在任何时候都只有一个单独的箭头显示。我使用clickhover方法来定义clickmouseentermouseexit事件的处理函数。

...
$("<a id=left></a><a id=right></a>").prependTo("form")
    .css({
        "background-image": "url(leftarrows.png)",
        "float": "left",
        "margin-top": "15px",
        display: "block", width: 50, height: 50
}).click(handleArrowPress).hover(handleArrowMouse)
...

handleArrowPresshandleArrowMouse函数是空的,但是我将在稍后填充它们。此时,我有两个a元素,它们都显示向左的箭头,并且在form元素中彼此相邻。我一起创建并格式化了a元素,因为大部分配置都是通用的,但是现在是时候移动并定制正确的按钮了,我的做法如下:

...
$("#right").css("background-image", "url(rightarrows.png)").appendTo("form");
...

我使用append方法将元素移动到form元素的末尾,并使用css方法将background-image属性更改为使用rightarrows.png。你可以在图 11-4 中看到这个图像。

9781430263883_Fig11-04.jpg

图 11-4 。rightarrows.png 形象

像这样使用组合图像是一种常见的技术,因为它避免了浏览器向服务器发出三个不同的请求来获取三个密切相关的图像的开销。当我很快填充handleArrowMouse函数时,您将看到如何使用这种图像。你可以在图 11-5 中看到页面的样子。

9781430263883_Fig11-05.jpg

图 11-5 。示例文档的中间状态

处理提交按钮

从图 11-5 可以看出,我的例子处于中间状态。新的特性出现了,但是我还没有恰当地处理一些现有的元素。其中最重要的是提交表单的 Place Order 按钮。清单 11-4 显示了脚本中处理这个元素的附加内容(并添加了一个新特性)。

清单 11-4 。处理提交按钮的

...
<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")
            .css({
                "background-image": "url(leftarrows.png)",
                "float": "left",
                "margin-top": "15px",
                display: "block", width: 50, height: 50
            }).click(handleArrowPress).hover(handleArrowMouse)

        $("#right").css("background-image", "url(rightarrows.png)").appendTo("form");

        $("h1").css({"min-width": "0", width: "95%",});
        $("#row2, #row3").hide();
        $("#oblock").css({float: "left", display: "inline", border: "thin black solid"});
        $("form").css({"margin-left": "auto", "margin-right": "auto", width: 885});

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

        function handleArrowMouse(e) {
        }

        function handleArrowPress(e) {

        }
    });
</script>
...

为了适应轮播按钮引起的布局变化,我将包含button元素的div(它有一个buttonDivid)移动到一个新的div元素中,这个元素又被追加到了body元素中。这会将按钮移动到返回页面底部的位置。我还添加了一个div和一个span元素。这些将用于显示用户选择的产品总数。

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

这一部分的下一个变化是隐藏两行产品。这样,当用户单击轮播按钮时,您就可以向用户展示它们。

...
$("#row2, #row3").hide();
...

我还调整了h1元素的样式,以匹配修改后的布局样式。

...
$("h1").css({"min-width": "0", width: "95%",});
...

你可以在图 11-6 中看到这些变化的效果。

9781430263883_Fig11-06.jpg

图 11-6 。处理提交按钮和整理 CSS

实现轮播事件处理函数

下一步是实现处理转盘按钮事件的函数。我将处理由handleArrowMouse函数处理的mouseentermouseexit事件。清单 11-5 显示了这个函数的实现。

清单 11-5 。处理箭头按钮鼠标事件

...
function handleArrowMouse(e) {
   var propValue = e.type == "mouseenter" ? "-50px 0px" : "0px 0px";
   $(this).css("background-position", propValue);
}
...

处理组合图像的技巧是使用background-position属性来移动图像,这样只有我想要的部分可见。虽然在我的箭头组中有三个图像,但我将只使用其中的两个。正常情况下会显示最暗的图像,当鼠标悬停在元素上时会显示中间的图像。你可以用剩下的箭头来表示一个按钮被点击或者被禁用,但是我想保持简单。你可以在图 11-7 中看到图像代表的两种状态。

9781430263883_Fig11-07.jpg

图 11-7 。箭头按钮的两种状态

handleArrowPress函数负责创建旋转木马效果,允许用户翻阅一排排鲜花。清单 11-6 显示了这个函数的实现。

清单 11-6 。实现 handleArrowPress 功能

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

该函数中的前三条语句设置了您需要的基本数据。

...
var elemSequence = ["row1", "row2", "row3"];
var visibleRow = $("div.drow:visible");
var visibleRowIndex = jQuery.inArray(visibleRow.attr("id"), elemSequence);
...

第一条语句为行元素定义了一组id属性值。第二条语句使用 jQuery 来获取可见行,然后我使用它来确定行id值数组中可见行的索引。(我使用了inArray效用方法,我在第三十四章中解释过。)所以,我知道哪一行是可见的,以及我在行序列中的位置。我的下一步是找出下一个将要显示的行的索引。

...
var targetRowIndex;
if (e.target.id == "left") {
    targetRowIndex = visibleRowIndex - 1;
    if (targetRowIndex < 0) {targetRowIndex = elemSequence.length -1};
} else {
    targetRowIndex = (visibleRowIndex + 1) % elemSequence.length;
}
...

在几乎任何其他编程语言中,我都可以使用模操作符来计算出要显示的下一行的索引,但是模数学的 JavaScript 实现不能正确地支持负值。所以,如果用户点击左键,我手动检查数组边界;如果用户点击了右边的按钮,我就使用%操作符。一旦我确定了当前可见的元素和接下来要显示的元素,我就使用 jQuery effects 来制作从一个元素到另一个元素的动画。

...
visibleRow.fadeOut("fast", function() {
    $("#" + elemSequence[targetRowIndex]).fadeIn("fast")});
...

我使用了fadeOutfadeIn方法,因为它们很适合我的 CSS 表格样式布局。我在第一个效果中使用回调来触发第二个效果,并使用fast时间跨度来执行这两个效果。页面的静态布局没有变化,但是箭头按钮现在将用户从一行花带到下一行花,如图图 11-8 所示。

9781430263883_Fig11-08.jpg

图 11-8 。提供产品行的传送带

合计产品选择

最后一个更改是连接项目 total,以便在各个输入字段中选择的鲜花总数显示在产品传送带下。清单 11-7 显示了对脚本的修改。

清单 11-7 。为产品总计布线

...
<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")
            .css({
                "background-image": "url(leftarrows.png)",
                "float": "left",
                "margin-top": "15px",
                display: "block", width: 50, height: 50
            }).click(handleArrowPress).hover(handleArrowMouse)

        $("#right").css("background-image", "url(rightarrows.png)").appendTo("form");

        $("h1").css({"min-width": "0", width: "95%",});
        $("#row2, #row3").hide();
        $("#oblock").css({float: "left", display: "inline", border: "thin black solid"});
        $("form").css({"margin-left": "auto", "margin-right": "auto", width: 885});

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

在这次添加中,我选择了文档中的input元素,并注册了一个处理函数,该函数从每个元素中获取值,对其求和,并将其设置为我之前添加的span元素的内容。你可以在图 11-9 中看到效果。

9781430263883_Fig11-09.jpg

图 11-9 。显示产品选择总数

总计显示了所有input元素的总和,而不仅仅是当前可见的元素(尽管使用其他方法也很简单)。

禁用 JavaScript

我对示例文档做了一些彻底的修改,但都是用 jQuery 做的。这意味着我已经有效地创建了两层文档,一层用于支持 JavaScript 的浏览器,一层用于不支持 JavaScript 的浏览器,图 11-10 显示了当你禁用 JavaScript 并查看示例文档时会发生什么。

9781430263883_Fig11-10.jpg

图 11-10 。禁用 JavaScript 并查看示例文档

我又回到了起点。通过一点规划和预先考虑,我可以为非 JavaScript 客户机提供一组功能,让它们仍然可以与您的页面或应用进行交互。这通常是一个好主意;有许多大公司集中管理 IT(信息技术),并禁用 JavaScript 作为安全预防措施。(嗯,算是吧。在为这样的组织工作多年后,我开始相信这些政策实际上并没有阻止员工使用 JavaScript 他们只是创造了寻找漏洞和变通办法的动机。)

摘要

在本章中,我向您展示了如何结合前几章的技术来重构示例文档。我以编程方式添加了新内容,创建了一个简单的产品转盘,并创建了一个显示所选项目的总数的汇总。在这个过程中,我调整了 DOM 和 CSS 以适应这些变化,所有这些都是为了让非 JavaScript 浏览器退回到仍然有用的文档。

在本书的下一部分,我将继续构建这个例子,引入更多的 jQuery 特性来充实功能。在大多数情况下,我会将这些应用到原始的示例文档中,以便依次关注每个特性,但是在第十六章中,我将再次重构示例以引入更多的特性。

十二、使用数据模板

在本书的前一版本中,我使用了 jQuery Templates 插件介绍了数据模板。这个插件的历史相当奇怪。微软和 jQuery 团队宣布,微软开发的三个插件已经被接受为“官方”插件,这是任何其他插件都没有的地位。过了一会儿,jQuery 团队宣布这些插件已被废弃,官方状态已被移除,并计划用其他功能替换它们。模板插件的替换将作为 jQuery UI 的一部分创建(我将在本书的第四部分描述)。

那是不久前的事了,新的官方 jQuery 模板引擎还没有完成,它被命名为 jsViews 。有一个测试版本,但它有粗糙的边缘,仍然不稳定。我不是一个粉丝,但是你可以在https://github.com/BorisMoore/jsviews了解更多信息并获得该库的最新测试版。

与此同时,我在上一版本中使用的不推荐使用的 jQuery Templates 包并没有很好地老化。不久前,我在自己的项目中停止使用它,选择了一个叫做Handlebars的替代品,它可以从http://handlebarsjs.com获得。它没有附带任何 jQuery 集成——事实上,它根本不是一个 jQuery 插件——但是很容易通过 jQuery 语法编写少量代码来支持模板,我将向您展示这是如何实现的。

手柄——就像之前的 jQuery 模板插件一样——支持小胡子模板,它们被表示为包含特殊小胡子指令的 HTML。我解释了这些是什么,但是术语小胡子的使用来自于这样一个事实,即它们是用括号字符({})来表示的,看起来有点像侧面的小胡子。表 12-1 提供了本章的总结。

表 12-1 。章节总结

问题 解决办法 列表
使用模板生成元素。 安装 Handlebars 库,创建一个 jQuery 插件,并使用template方法。 1–6
将从模板生成的元素分配给不同的父元素。 或者拆分源数据并应用模板两次,或者使用 slice filter 和 end 方法分割生成的元素。 7–10
如果定义了数据属性并且不为空,则更改模板的输出。 使用内置的#if#unless模板助手。 11, 12
枚举数组的内容或对象的属性。 使用#each模板助手。 13, 14
引用模板中数据对象的另一部分。 使用#with模板助手,或者使用../路径。 15–17
创建自定义模板助手。 使用Handlebars.registerHelper方法注册助手的名称和返回模板内容的助手函数。 18–22
在模板帮助函数中接收可选参数。 使用options.hash属性。 23, 24
定义可以在自定义模板帮助器的块中使用的特殊属性。 使用options.data属性。 25, 26

了解模板解决的问题

数据模板解决了一个特定的问题:它们允许您以编程方式从 JavaScript 对象的属性和值生成元素。这是你可以用其他方式做的事情,事实上,我在第十一章中做了类似的事情,我在示例文档中创建了一些元素来表示额外的花。清单 12-1 显示了该章的相关陈述。

清单 12-1 。以编程方式创建元素

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

清单中的语句很难阅读,对于更复杂的元素,难度会急剧增加。正如我将解释的那样,数据模板很方便地将重点放回到 HTML 上,并最大限度地减少了从数据生成元素所需的代码量。

从更广的角度来看,将数据集成到文档中是一个需要解决的常见问题。在我的项目中,它通过两种情况出现。第一个原因是因为我正在使用一些预先存在的系统,这些系统包含驱动我的 web 应用的数据。我可以获得数据,并在服务器上将其集成到文档中——有一些很好的技术可以做到这一点——但这意味着我的服务器群要花很多时间做我可以让浏览器为我做的工作。如果您曾经构建和运行过一个大容量的 web 应用,您就会知道成本是巨大的,并且任何减少所需处理量的机会都会被认真考虑。

我需要将数据集成到文档中的第二个原因是,我的 web 应用通过 Ajax 获取数据来响应用户操作。我将在第十四章和第十五章中全面解释 jQuery 对 Ajax 的支持,但简单来说,你可以从服务器获取并显示数据,而无需在浏览器中重新加载整个页面。这是一种广泛使用的强大技术,数据模板与它配合得很好。

设置模板库

在使用模板之前,您必须下载模板库并在文档中链接到它。您可以从http://handlebarsjs.com下载这个库,我将 JavaScript 代码保存到一个名为handlebars.js的文件中,与 jQuery JavaScript 文件放在一起。

handlebars 库与 jQuery 没有任何集成,但是创建一个允许我使用 jQuery 语法生成模板的 jQuery 插件很简单。我创建了一个名为handlebars-jquery.js的文件,并用它来定义清单 12-2 中的代码。

清单 12-2 。改编 handlerbars.js 以使用 handler bars-jQuery . js 文件中的 jQuery 语法

(function ($) {
    var compiled = {};
    $.fn.template = function (data) {
        var template = $.trim($(this).first().html());
        if (compiled[template] == undefined) {
            compiled[template] = Handlebars.compile(template);
        }
        return $(compiledtemplate);
    };
})(jQuery);

创建自定义 JQUERY 插件

正如清单 12-2 所示,为 jQuery 创建插件很简单,尤其是如果你只是在一个已有库的基础上创建一个包装器。我没有在本书中描述定制插件是如何工作的,因为这是很少有开发者需要做的事情,但是你可以在http://learn.jquery.com/plugins获得完整的细节。

这段代码定义了一个名为template的方法,可以在 jQuery 对象上调用该方法,使用 handlebars 将模板应用于数据对象。结果是一个 jQuery 对象,包含从模板生成的 HTML 元素。为了生成模板,我需要为示例文档中的handlebars.jshandlebars-jquery.js文件添加script元素,如清单 12-3 所示。

清单 12-3 。将库添加到示例文档中

<!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 type="text/javascript">
        $(document).ready(function () {

            // ...
*example code will go here*...

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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>

我将使用这个清单作为本章的示例文档。除了添加模板库和我的简单插件,您会注意到我已经删除了描述单朵花的元素——我这样做是为了探索使用模板添加它们的不同技术。你可以在图 12-1 中看到这个初始的 HTML 文档是如何出现在浏览器中的。

9781430263883_Fig12-01.jpg

图 12-1 。起始示例文档

第一数据模板示例

开始学习数据模板的最好方法是直接进入。清单 12-4 展示了基本的模板特性。我在这个清单中包含了完整的 HTML 文档,因为模板是用一个script元素表示的,但是我将只展示后续清单中需要的更改。

清单 12-4 。第一数据模板示例

<!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}}" data-price="{{price}}" data-stock="{{stock}}"
                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 template = $("#flowerTmpl").template(data).appendTo("#row1");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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>

在接下来的小节中,我将分解这个例子并解释每一部分。

image 提示当数据是文档的一部分时,称为内联数据。另一种方法是远程数据,这种方法可以从独立于文档的服务器上获取数据。我将在本章后面演示远程数据,但它涉及到了 Ajax 的 jQuery 支持,这是第十四章和第十五章的主题。

定义数据

该示例的起点是数据,在本例中是一个具有单个属性的对象,该属性被设置为一个对象数组。每个对象描述一个花卉产品,清单 12-5 显示了示例 HTML 文档中的相关语句。

清单 12-5 。定义花卉数据

...
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 }]
};
...

手柄模板作用于对象和属性,这就是为什么我必须在 flower 对象数组周围有一个对象包装器。对于这个示例,数组包含六个对象,每个对象都有一组描述花店产品的属性:显示名称、产品名称、库存水平和价格。

定义模板

正如您所想象的,数据模板库的核心是数据模板。这是一组包含占位符的 HTML 元素,这些占位符对应于数据对象的各个方面。清单 12-6 显示了这个例子的模板。

清单 12-6 。定义数据模板

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

关于模板要注意的第一点是,它包含在具有text/x-handlebars-templatetype属性的script元素中。这是为了阻止浏览器解释模板的内容。第二点需要注意的是,我已经使用id属性为我的脚本元素指定了一个名称:在这个例子中,模板名为flowerTmpl。这个名称很重要,因为我将需要它来将模板应用于数据。

模板的内容将应用于数据数组中的对象,以便为每个对象生成一组 HTML 元素。您可以看到模板的结构与我在前几章中用于花卉产品的元素集相对应。

当然,关键的区别是我在清单中强调的部分。这些是双小胡子指令(之所以这么叫是因为用来表示它们的括号字符看起来像小胡子——因此有了术语小胡子模板)。在这个例子中有两种类型的指令(我将在本章后面介绍更多的类型)。

第一种指令是一个部分,它定义了一个模板区域,该区域将为数据对象中具有相同名称的每个属性值生成。Section 指令以一个#字符开始({{#flowers}},在本例中),以一个/字符结束({{/flowers}})。我使用的 section 指令将为分配给flowers属性的每个对象生成它所包含的模板部分。

另一种类型的指令是一个变量,它将被数据对象中相应的属性值替换。例如,当模板库遇到{{product}}变量时,它用正在处理的对象的product属性的值替换它,这意味着模板的一部分是这样的:

...
<img src="{{product}}.png"/>
...

转化成了这样:

...
<img src="aster.png"/>
...

应用模板

我使用清单 12-2 中的 jQuery 插件中定义的template方法将模板应用于数据。下面是示例 HTML 文档中对template方法的调用:

...
var template = $("#flowerTmpl").template(data).appendTo("#row1");
...

我使用 jQuery $函数选择包含模板的script元素,然后调用结果jQuery对象上的template方法,传入我想要处理的数据。

template方法返回一个标准的jQuery对象,其中包含从模板中产生的元素。在这种情况下,我得到了一组div元素,每个元素包含一个imglabelinput元素,它们是为我的数据数组中的一个对象定制的。我使用appendTo方法将整个集合作为子元素插入到row1元素中。你可以在图 12-2 中看到结果。

9781430263883_Fig12-02.jpg

图 12-2 。使用数据模板

调整结果

我没有得到我想要的结果——所有的花都在一行,而不是像前几章那样分成两行。但是,因为我正在处理一个 jQuery 对象,所以我可以使用第二部分中描述的技术来分割元素。清单 12-7 展示了如何通过对template方法返回的jQuery对象进行操作来做到这一点。

清单 12-7 。处理来自模板的结果

...
<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 }]
        };

        $("#flowerTmpl").template(data)
            .slice(0, 3).appendTo("#row1").end().end().slice(3).appendTo("#row2")
    });
</script>
...

在这个例子中,我使用sliceend方法来缩小和扩大选择,使用appendTo方法将从模板生成的元素子集添加到不同的行。

请注意,我必须连续调用两次end方法来消除由slideappendTo方法引起的收缩。你可以在图 12-3 中看到效果——我更接近了,但是我仍然没有得到我想要的结果。

9781430263883_Fig12-03.jpg

图 12-3 。试图调整从数据生成的 HTML 的布局

问题是 Handlebars 在我的模板内容被拆分成多行的地方向它生成的 HTML 添加了文本节点——由于我对slice方法的使用适用于模板生成的所有元素(包括文本节点),所以我在错误的地方拆分了内容。

有几种方法可以解决这个问题。第一种方法是调整模板,使所有的内容都在一行上——但是我不喜欢这种方法,我更喜欢让模板尽可能的易读。

另一种方法是调整传递给slice方法的索引,以考虑文本节点——但我也不喜欢这样,因为不是所有的文本编辑器都以创建文本节点的方式表示新行,这意味着用不同的编辑器编辑 HTML 文件会改变 JavaScript 代码的行为,这远非理想。

我的首选方法是在将模板添加到 DOM 之前,使用 jQuery 从模板生成的 HTML 中删除文本节点。不幸的是,jQuery 没有包含一个有用的方法来完成这个任务,所以最好的方法是使用带有*选择器的filter方法,它匹配所有的 HTML 标签类型,但是不包括文本节点。您可以在清单 12-8 的中看到我的 jQuery 中添加了 filter 方法。

清单 12-8 。使用 filter 方法删除文本节点

...
$("#flowerTmpl").template(data).filter("*")
    .slice(0, 3).appendTo("#row1").end().end().slice(3).appendTo("#row2")
...

你可以在图 12-4 中看到结果:花被正确地分成两行。

9781430263883_Fig12-04.jpg

图 12-4 。使用 filter 方法删除文本节点

我仍然不满意我处理模板生成的 HTML 的方式。我通常喜欢使用end方法来创建单语句操作,但是我发现end().end()序列令人讨厌。相反,我通常会将这些步骤分解成单独的操作,如清单 12-9 所示,它产生的结果与清单 12-8 相同,但是更容易阅读。

清单 12-9 。使用多条语句拆分元素

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

调整输入

另一种方法是调整传递给template方法的数据。清单 12-10 展示了如何做到这一点。

清单 12-10 。使用数据调整模板的输出

...
<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 tElem = $("#flowerTmpl");
        tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
        tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
    });
</script>
...

在这个脚本中,我通过两次使用模板来解决将花分配给行的问题——每行一次。我使用了split方法,这样我每次都可以向模板提供一系列数据对象。手法不同,但结果是一样的,如图图 12-4 。请注意,我必须注意保留我传递给template方法的对象的形状,以便它匹配我的节声明——我必须确保该对象有一个名为flowers的属性,该属性被设置为我要处理的数据对象的数组。

使用模板逻辑

区分各种 JavaScript 模板引擎的一种方法是查看模板的输出如何根据不同的数据值而变化。

一个极端是无逻辑模板 ,它不包含任何逻辑,改变模板的输出意味着在使用模板引擎之前仔细准备数据。另一个极端是全逻辑模板 ,这就像有一个简单的编程语言专用于定义和执行模板,内置对条件语句、循环、数组处理和管理数据集合的支持。

对于模板中应该包含多少逻辑,人们意见不一,整个话题都是有争议的。我喜欢处于中间的某个位置——我喜欢能够在我的模板中使用逻辑,但是我希望保持简单,并且不需要在我的 HTML 文档中添加另一种语言。我为这一章选择 Handlebars 库的原因之一是,它允许你在一个模板中使用尽可能少或尽可能多的逻辑——而且,正如你将在这一章的后面看到的,它使定义定制逻辑来解决特定问题变得容易。手柄库包含一些内置的助手 ,在表 12-2 中描述,它们是简单的逻辑操作符,可以用来根据数据值改变模板的输出。

表 12-2 。内置车把助手

助手 描述
#if 一个if / then / else条件,如果指定的属性存在并且不是null,则计算结果为true
#unless #if辅助者的逆;如果指定的属性不存在或者是null,则计算结果为true
#each 迭代对象数组或对象的属性。
#with 为模板的一部分设置上下文。

创建条件内容

为了演示如何在 Handlebars 模板中使用逻辑,我将根据数据对象的flowers数组中每个对象的stock属性的值来设置模板中input元素的value属性。我的目标是当相应的股票属性大于零时,将value设置为1。通过应用清单 12-11 中的#if助手,你可以看到我是如何做到这一点的。

清单 12-11 。使用模板逻辑改变模板的输出

...
<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="{{#if stock}}1{{else}}0{{/if}}"required />
    </div>
    {{/flowers}}
</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 }]
        };
        for (var i = 0; i < data.flowers.length; i++) {
            if (data.flowers[i].stock == 0) {
                data.flowers[i].stock = null;
            }
        }
        var tElem = $("#flowerTmpl");
        tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
        tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
    });
</script>
...

助手的每一部分都用双胡子表示:第一部分由#if组成,后面是我想要检查的属性,在本例中是stock。如果由#flowers段指令处理的当前对象定义了一个stock属性并且该属性不是null,那么这将被评估为true。如果是这种情况,模板引擎将把条件的第一部分之后的值插入到 HTML 中,在本例中是1

可选的else部分的工作方式就像在 JavaScript 中一样,它允许我提供一个替代值,如果当前对象没有stock属性(如果有stock属性并且是null),那么这个替代值将被使用。在这种情况下,模板引擎会将0插入到模板生成的 HTML 中。最后一段是/if,表示条件块的结束。

#if助手背后的逻辑是基本的,根据属性是否存在和是否被定义返回true,迫使我在将数据传递给template方法之前处理数据。我使用一个 JavaScript for循环来枚举 flower 对象,并设置任何值为0nullstock属性。结果是,从模板生成的所有input元素的值都是1,除了Peony的元素,如图 12-5 所示。

9781430263883_Fig12-05.jpg

图 12-5 。使用模板中的逻辑来改变生成的 HTML

image 提示我不喜欢处理数据来适应模板引擎的限制,在本章的后面,我将向您展示如何创建不需要数据处理的定制逻辑。

#unless辅助对象的工作方式与#if辅助对象相同,但是如果它所应用的属性不存在或者为空,那么它将被评估为true。在清单 12-12 中,您可以看到我是如何在没有可选的else指令的情况下,将#if#unless助手一起应用于设置模板中input元素的value属性的。

清单 12-12 。在模板中应用#if 和#unless 助手

...
<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="{{#if stock}}1{{/if}}{{#unless stock}}0{{/unless}}"required />
    </div>
    {{/flowers}}
</script>
...

#if#unless助手彼此完全独立,但是以这种方式使用它们展示了如何在不使用else指令的情况下测试属性的存在或不存在。该清单产生的结果与清单 12-11 中的相同,如图 12-5 中的所示。

枚举数组和属性

我在清单 12-12 中使用的 section 指令是 Handlebars 库支持更广泛使用的 mustache 模板的一部分,而#each助手是一个更复杂的替代工具,它提供了一些可以在模板中使用的特殊属性。这些属性在表 12-3 中描述。

表 12-3 。#each Helper 提供的特殊属性

名字 描述
this 返回正在处理的对象。
@index 当在数组上使用#each辅助对象时,返回当前对象的索引。
@key 当用于对象时,返回当前属性的名称。

在清单 12-13 中,您可以看到我是如何在现有数据上使用#each助手以及@index属性的。

清单 12-13 。使用#each 助手和@index 属性

...
<script id="flowerTmpl" type="text/x-handlebars-template">
    {{#each flowers}}
    <div class="dcell">
        <label>Position: {{@index}}</label>
        <img src="{{product}}.png"/>
        <label for="{{product}}">{{name}}:</label>
        <input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
            value="{{#if stock}}1{{/if}}{{#unless stock}}0{{/unless}}" required />
    </div>
    {{/each}}
</script>
...

我把要枚举的对象的源指定为#each助手的参数,在这种情况下是传递给template方法的数据对象的flowers属性的值。你可以在图 12-6 中看到结果(索引为每一行重新开始,因为我将数据分成两部分并调用模板方法两次,如清单 12-11 所示)。

9781430263883_Fig12-06.jpg

图 12-6 。使用#each 助手和@index 属性

当将对象传递给template方法而不是数组时,this@key属性很有用。Handlebars 库将枚举对象的属性——@key属性用于获取当前属性名,this属性用于获取当前值。您可以在清单 12-14 中看到这两个属性一起使用。

清单 12-14 。用#each 助手枚举对象的属性

<!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="flowerListTmpl" type="text/x-handlebars-template">
        <ul>
            {{#each stockData}}
                <li>{{@key}} ({{this}} in stock)</li>
            {{/each}}
        </ul>
    </script>
    <script type="text/javascript">
        $(document).ready(function () {

            var data = {
                stockData: {
                    Aster: 10, Daffodil: 12, Rose: 2,
                    Peony: 0, Primula: 1, Snowdrop: 15
                }
            };
            $("#flowerListTmpl").template(data).appendTo("form");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
    </form>
</body>
</html>

我用一个更简单的结构替换了这个例子中的数据——data.stockData属性返回一个对象,该对象的属性名描述鲜花,其值描述库存数量。对于模板,我在stockData属性上使用了#each助手(记住,我必须将一个对象传递给template方法,并将助手和指令应用到它的属性上)。本例中的模板创建了一个列表。我用this属性获取每个属性的值,用@key属性获取属性的名称,你可以在图 12-7 中看到结果。

9781430263883_Fig12-07.jpg

图 12-7 。将@键和 this 属性与#each 帮助器一起使用

更改数据上下文

数据上下文 是助手和变量应用到的数据对象的一部分。当模板处理开始时,上下文是整个数据对象,但它会被 helpers 和 section 指令更改,以使编写模板更容易,如我在上一个示例中使用的模板所示:

...
{{#each stockData}}
    <li>{{@key}} ({{this}} in stock)</li>
{{/each}}
...

#each助手依次将上下文转移到数据对象的每个属性——这意味着在#each助手块中定义的变量和助手都将相对于当前对象进行计算。你可以在清单 12-15 中更清楚地看到这一点,在那里我更改了数据和模板以使上下文的效果更清晰。

清单 12-15 。强调模板中上下文的作用

...
<script id="flowerListTmpl" type="text/x-handlebars-template">
    <ul>
        <h3>{{title}}</h3>
        {{#each stockData}}
            <li>{{description.Name}} ({{description.Stock}} in stock)</li>
        {{/each}}
    </ul>
</script>
<script type="text/javascript">
    $(document).ready(function () {

        var data = {
            title: "Stock List",
            stockData: {
                aster: {
                    description: { Name: "Aster", Stock: 10 }
                },
                daffodil: {
                    description: { Name: "Daffodil", Stock: 12 }
                },
                rose: {
                    description: { Name: "Rose", Stock: 2 }
                }
            }
        };
        $("#flowerListTmpl").template(data).appendTo("form");
    });
</script>
...

我已经为数据对象添加了一个title属性,并为单朵花添加了结构。模板的第一个指令依赖于默认的数据上下文,即整个数据对象:

...
<h3>{{title}}</h3>
...

您可以理解我所说的与上下文相关的被评估的指令的意思——我只需要指定属性名,而不需要任何限定来从数据对象中获取属性的值。模板中的下一条指令改变了上下文:

...
{{#each stockData}}
...

#each帮助器枚举stockData属性返回的对象的属性,并依次将上下文更改为每个属性值。您可以在模板的下一行看到效果:

...
<li>{{description.Name}} ({{description.Stock}} in stock)</li>
...

我访问相对于当前上下文的NameStock属性——这意味着使用一个路径来浏览description对象,遵循数据对象的结构。结果如图 12-8 中的所示。

9781430263883_Fig12-08.jpg

图 12-8 。访问与上下文相关的属性

使用#with 助手

在前面的例子中,我必须指定description.Namedescription.Stock来访问模板的属性值。#with助手可以通过改变它包含的所有指令的数据上下文来消除复制公共属性名的需要,如清单 12-16 所示。

清单 12-16 。使用#with Helper 更改数据上下文

...
<script id="flowerListTmpl" type="text/x-handlebars-template">
    <ul>
        <h3>{{title}}</h3>
        {{#each stockData}}
            {{#with description}}
                <li>{{Name}} ({{Stock}} in stock)</li>
            {{/with}}
        {{/each}}
    </ul>
</script>
...

#with块的范围内,上下文被缩小到description属性。当结合由#each助手所做的数据上下文的改变时,我能够访问每个花对象的NameStock属性,而不必限定名称。

访问父数据上下文

数据上下文的变化并不总是有用的,有时您需要访问数据对象的另一部分来获得一个公共属性值。您可以通过在变量前加前缀../来导航到父上下文,如清单 12-17 所示。

清单 12-17 。访问父数据上下文

...
<script id="flowerListTmpl" type="text/x-handlebars-template">
    <ul>
        <h3>{{title}}</h3>
        {{#each stockData}}
            {{#with description}}
                <li>{{Name}}{{../../prefix}}{{Stock}}{{../../suffix}}</li>
            {{/with}}
        {{/each}}
    </ul>
</script>
<script type="text/javascript">
    $(document).ready(function () {

        var data = {
            title: "Stock List",
            prefix: " (",
            suffix: " in stock)",
            stockData: {
                aster: {
                    description: { Name: "Aster", Stock: 10 }
                },
                daffodil: {
                    description: { Name: "Daffodil", Stock: 12 }
                },
                rose: {
                    description: { Name: "Rose", Stock: 2 }
                }
            }
        };
        $("#flowerListTmpl").template(data).appendTo("form");
    });
</script>
...

我已经在数据对象的顶层定义了prefixsuffix属性。要从模板中访问这些,我需要向上导航两个上下文级别,如下所示:

...
{{../../prefix}}
...

这个指令出现在#with助手的范围内,所以应用一次../将会把上下文改变到模板中的下一级,也就是#each助手。#each助手已经将上下文设置为stockData属性的内容,所以我需要再升一级才能到达prefix属性,这意味着我必须引用../../prefix来获得我想要的值。

image 提示../序列在模板而不是数据对象中向上导航一级。

创建自定义模板助手

一些助手中的逻辑非常简单,这意味着经常需要处理数据以适应助手的工作方式。你可以在清单 12-18 的中看到一个例子,它重复了我在本章前面用来演示#if助手的例子。

清单 12-18 。准备数据供模板助手使用

<!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">
        {{#flowers}}
        <div class="dcell">
            <img src="{{product}}.png"/>
            <label for="{{product}}">{{name}}:</label>
            <input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
                value="{{#if stock}}1{{else}}0{{/if}}"required />
        </div>
        {{/flowers}}
    </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 }]
            };
            for (var i = 0; i < data.flowers.length; i++) {
                if (data.flowers[i].stock == 0) {
                    data.flowers[i].stock = null;
                }
            }
            var tElem = $("#flowerTmpl");
            tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
            tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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>

我不喜欢在将数据传递给模板引擎之前处理它,因为这意味着我有逻辑在两个地方显示我的数据:模板和for循环。

这就是模板的使用变得有争议的地方,因为有两种方法可以确保从模板生成内容的逻辑只在一个地方:从模板中移除逻辑并全部用 JavaScript 定义,或者移除for循环并向模板添加额外的逻辑。有许多论据支持这两种方法,但本质上这是由偏好和您正在处理的数据的性质驱动的个人选择。我更喜欢在模板中添加逻辑,这是Handlebars库使之变得简单和容易的事情。

创建条件模板助手

在清单 12-19 中,您可以看到我对handlebars-jquery.js文件所做的添加,为我的模板创建一个定制的助手。

image 提示你可以在任何脚本元素或 JavaScript 文件中定义自定义逻辑。我喜欢把我添加到 Handlebar.js 的东西放在一个地方——但这只是个人偏好。

清单 12-19 。在 handlebars-jquery.js 文件中为车把定义自定义条件逻辑

(function ($) {

    Handlebars.registerHelper('gt', function (a, b, options) {
        return (a > b) ? options.fn(this) : options.inverse(this);
    });

    var compiled = {};
    $.fn.template = function (data) {
        var template = $.trim($(this).first().html());
        if (compiled[template] == undefined) {
            compiled[template] = Handlebars.compile(template);
        }
        return $(compiledtemplate);
    };
})(jQuery);

Handlebars 库定义了一个名为Handlebars的全局对象,该对象又定义了一个名为registerHelper的方法。registerHelper 有两个参数——您想给助手取的名字和一个助手函数,当在模板中遇到助手时将调用这个函数。我已经调用了我的自定义助手gt(大于的的缩写),但是演示助手函数如何工作的最简单的方法是演示我的自定义助手,然后解释它的行为如何与其定义相关联。在清单 12-20 中,你可以看到我是如何将我的gt助手应用到示例 HTML 文档中的。

清单 12-20 。应用自定义模板助手

...
<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="{{#gt stock 0}}1{{else}}0{{/gt}}" required />
    </div>
    {{/flowers}}
</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 tElem = $("#flowerTmpl");
        tElem.template({ flowers: data.flowers.slice(0, 3) }).appendTo("#row1");
        tElem.template({ flowers: data.flowers.slice(3) }).appendTo("#row2");
    });
</script>
...

image 提示你可能需要在浏览器中重新加载网页才能看到正确的效果。JavaScript 文件有时会被大量缓存,这可能会阻止更改生效。

我的 #gt助手检查stock属性的值是否大于零。如果是,则将1插入模板,否则插入0

在从模板生成内容之前,我必须调用Handlebars.registerHelper方法,以便 Handlebars 库知道#gt助手。当遇到 helper 指令时,Handlebars 将把指令中所有跟在#gt后面的值作为参数传递给我的函数,替换掉当前数据上下文中任何可以解析为变量的值。

在清单中,我将对#gt助手的引用放在一个 section 指令中,这意味着 Handlebars 将枚举data.flowers数组,而stock属性将被当前 flower 的值所替换——这意味着,例如,对于 Aster,传递给我的助手函数的参数将是10、Aster stock属性的值和1,它们不能被解析为数据值,因此不做任何更改。

image 提示你可以根据需要给你的帮助函数提供或多或少的参数。两个正是我需要进行基本比较的数字。

这些是我在助手函数中收到的ab值。我还收到一个由车把提供的options物体:

...
Handlebars.registerHelper('gt', function (a, b, options) {
    return (a > b) ? options.fn(this) : options.inverse(this);
});
...

options对象提供了对编写助手有用的特性,如表 12-4 所述。

表 12-4 。由 Options 对象定义的属性和方法

名字 描述
fn(data) 调用以获取为条件帮助器的true结果定义的内容,或者没有else指令的帮助器的唯一内容。
inverse(data) 被调用以获取已经在助手的else子句中定义的内容。
Hash 用于将可选参数接收到 helper 函数中。
Data 用于提供具有特殊属性的模板。

在我的#gt助手中,如果a大于b(在本例中,这意味着当前 flower 的股票属性的值大于零),我调用options.fn方法来获取应该插入到 HTML 中的内容。我传递了this变量,这个变量被设置为当前的数据上下文。如果a不大于b,那么我改为调用options.inverse方法。对于这个例子,options.fn方法将返回1,而options.inverse方法将返回0

返回更复杂的内容

插入 HTML 的内容是助手函数的结果,这意味着我可以通过插入更大的 HTML 片段来增加助手函数的复杂性,从而简化模板。在清单 12-21 中,您可以看到我是如何在handlebars-jquery.js文件中定义一个名为 #gtValAttr的助手的。

清单 12-21 。在 handlebars-jquery.js 文件中定义更复杂的助手

(function ($) {

    Handlebars.registerHelper('gt', function (a, b, options) {
        return (a > b) ? options.fn(this) : options.inverse(this);
    });

    Handlebars.registerHelper("gtValAttr", function () {
        return "value='" + (this.stock > 0 ? "1" : "0") + "'";
    });

    var compiled = {};
    $.fn.template = function (data) {
        var template = $.trim($(this).first().html());
        if (compiled[template] == undefined) {
            compiled[template] = Handlebars.compile(template);
        }
        return $(compiledtemplate);
    };
})(jQuery);

新的助手根本不接受任何参数——它通过this属性获得所需的值(正如我提到的,该属性被设置为当前数据上下文)。helper 函数的结果是一个对value属性的完整定义,它是为使用它的模板定制的。在清单 12-22 的中,你可以看到我是如何在模板中应用#gtValAttr助手的。

清单 12-22 。在模板中应用#gtValAttr 助手

...
<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}}"
            {{#gtValAttr}}{{/gtValAttr}}required />
    </div>
    {{/flowers}}
</script>
...

image 注意我已经向您展示了这个助手来演示 Handlebars 库提供的灵活性,但是我并没有在实际项目中使用这种助手。它太依赖于数据和模板的结构,对其中任何一个的改变都会破坏代码。我更喜欢创建像#gt这样的小而集中的助手,它们可以在任何模板中使用,并且生成尽可能少的内容,最好是通过参数提供。

在帮助函数中接收可选参数

您可以在模板中定义传递给 helper 函数的参数。这些可选参数的用途完全取决于助手的创建者,但最常见的用途是将属性值传递给生成完整 HTML 元素的助手。在清单 12-23 中,你可以看到我已经定义了一个助手,它从一个花数据对象中创建完整的input元素。同样,这也不是我喜欢在自己的项目中使用模板的方式,但是读者可能更喜欢助手中的复杂性,而不是模板中的复杂性。

清单 12-23 。在 handlebars-jquery.js 文件中定义一个接受可选参数的助手

(function ($) {

    Handlebars.registerHelper('gt', function (a, b, options) {
        return (a > b) ? options.fn(this) : options.inverse(this);
    });

    Handlebars.registerHelper("gtValAttr", function () {
        return "value='" + (this.stock > 0 ? "1" : "0") + "'";
    });

    Handlebars.registerHelper("inputElem", function (product, stock, options) {
        options.hash.name = product;
        options.hash.value = stock > 0 ? "1" : "0";
        options.hash.required = "required";
        return $("<input>", options.hash)[0].outerHTML;
    });

    var compiled = {};
    $.fn.template = function (data) {
        var template = $.trim($(this).first().html());
        if (compiled[template] == undefined) {
            compiled[template] = Handlebars.compile(template);
        }
        return $(compiledtemplate);
    };
})(jQuery);

#inputElem助手为一朵花生成了一个完整的input元素——但是再一次,通过查看它在模板中的应用将更容易理解它是如何工作的,如清单 12-24 所示。

清单 12-24 。在模板中应用#inputElem 助手

...
<script id="flowerTmpl" type="text/x-handlebars-template">
    {{#flowers}}
    <div class="dcell">
        <img src="{{product}}.png"/>
        <label for="{{product}}">{{name}}:</label>
        {{#inputElem product stock data-stock=stock data-price=price}}{{/inputElem}}
    </div>
    {{/flowers}}
</script>
...

正如你在清单 12-23 中看到的,#inputElem助手函数有两个参数,当我在模板中应用助手时,我用它们来传递productstock属性的值。附加参数的形式是key=value,它们通过options.hash属性传递给帮助函数,首先根据当前数据上下文进行解析。对于我的例子,这意味着options.hash属性为Aster花返回一个类似这样的对象:

{"data-stock": 10, "data-price": 2.99}

在第二部分中,我解释了 jQuery $函数的一个版本,它从 HTML 字符串和属性的 map 对象中生成一个jQuery对象,而option.hash对象正是我创建想要的input元素所需的格式。但是它不包含我需要的所有属性,所以我使用下面的语句来完成这个集合:

...
options.hash.name = product;
options.hash.value = stock > 0 ? "1" : "0";
options.hash.required = "required";
...

我可以使用 jQuery $函数创建我的input元素,应用属性,并返回模板所需的 HTML 字符串,如下所示:

...
return $("<input>", options.hash)[0].outerHTML;
...

为了获取 HTML 字符串,我使用一个数组索引器获取索引0处的HTMLElement对象,并使用outerHTML属性获取如下字符串:

<input data-stock="10" data-price="2.99" name="aster" value="1" required="required">

Handlebars 和 jQuery 配合得很好,使得从助手中生成完整的 HTML 元素变得很容易——#inputElem助手演示了这一点。

image 提示使用 jQuery 获取元素的 HTML 没有便捷的方法,这就是我使用底层HTMLElement对象的outerHTML属性的原因。最接近的方法是html,但是它返回一个元素的 HTML 内容,而不是元素本身的 HTML,这意味着在使用html方法之前,我必须将我的input元素附加到另一个元素上,就像这样:$("<div />").append($("<input>", options.hash)).html();我发现使用 HTML 元素更简单,更容易阅读。

提供自定义模板属性

我在本章前面解释过,#each助手定义了在它定义的块中可用的特殊属性。这也是您可以在自定义帮助器中完成的事情,这是简化模板结构的好方法。作为示范,我已经创建了#stockValue助手,如清单 12-25 所示。

清单 12-25 。在 handlebars-jquery.js 文件中创建#stockValue 助手

(function ($) {

    Handlebars.registerHelper('gt', function (a, b, options) {
        return (a > b) ? options.fn(this) : options.inverse(this);
    });

    Handlebars.registerHelper("gtValAttr", function () {
        return "value='" + (this.stock > 0 ? "1" : "0") + "'";
    });

    Handlebars.registerHelper("inputElem", function (product, stock, options) {
        options.hash.name = product;
        options.hash.value = stock > 0 ? "1" : "0";
        options.hash.required = "required";
        return $("<input>", options.hash)[0].outerHTML;
    });

    Handlebars.registerHelper("stockValue", function (options) {
        options.data.attributeValue = this.stock > 0 ? "1" : "0";
        return options.fn(this);
    });

    var compiled = {};
    $.fn.template = function (data) {
        var template = $.trim($(this).first().html());
        if (compiled[template] == undefined) {
            compiled[template] = Handlebars.compile(template);
        }
        return $(compiledtemplate);
    };
})(jQuery);

这是一个简单的助手,它所做的就是在options.data对象上创建一个名为attributeValue的属性,并为它分配我想要的值,作为input元素的value属性。我可以使用名为@attributeValue 的特殊属性在模板中的#stockValue助手包含的模板块中访问这个值,如清单 12-26 所示。

清单 12-26 。访问模板中的特殊属性

...
<script id="flowerTmpl" type="text/x-handlebars-template">
    {{#flowers}}
    <div class="dcell">
        <img src="{{product}}.png"/>
        <label for="{{product}}">{{name}}:</label>
        {{#stockValue}}
            <input name="{{product}}" data-price="{{price}}" data-stock="{{stock}}"
                value="{{@attributeValue}}" required />
        {{/stockValue}}
    </div>
    {{/flowers}}
</script>
...

摘要

在这一章中,我介绍了 Handlebars 模板库,它提供了一组很好的特性,可以将 JavaScript 数据转换成 HTML 元素,而不会陷入大量讨厌的代码中。我喜欢使用这个库的原因是,它提供了灵活性,可以决定在模板中定义多少逻辑,通过处理数据处理多少逻辑,以及在处理程序中隐藏多少逻辑。在下一章中,我将向您展示 jQuery 如何支持 HTML 表单,以及如何应用一个广泛使用的插件来验证用户输入的数据。

十三、使用表单

在这一章中,我将展示 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) Blur form元素失去焦点时触发。
change(function) Change form元素的值改变时触发。
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,尽管您会看到它并不是真正的底层,而且实际上使用起来很愉快。`

十五、使用 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
指定多个函数来处理成功、不成功或已完成的请求 successerrorcomplete设置指定一组函数 Ten
successerrorcomplete设置的函数中,指定将分配给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 对象的属性定义了一组键/值对,每个键/值对为请求配置一个设置。

image 本章依赖于第十四章中使用的相同Node.js脚本。

在这个例子中,我的 map 对象有一个属性——success——指定请求成功时调用的函数。我从服务器请求mydata.json文件,并使用它和一个数据模板来创建元素并将其插入到文档中,就像我在上一章中用速记方法所做的一样。默认情况下,ajax方法会发出一个 HTTP get请求,这意味着该示例相当于使用了getgetJSON方法,我在第十四章中向您展示过。(我将在“发出 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() 终止请求

image 提示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"}]

image 提示我很少使用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 方法,但是您可能很难使用除了GETPOST之外的任何方法,因为许多防火墙和应用服务器被配置为丢弃其他类型的请求。如果您想使用其他 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 请求成功时将调用的函数

image 提示表 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元素,显示statuserrorMsg参数的值,如图 15-1 中的所示。

9781430263883_Fig15-01.jpg

图 15-1 。显示错误信息

status参数可以是表 15-4 中显示的值之一。

表 15-4 。错误状态值

环境 描述
abort 表示请求被中止(使用jqXHR对象)
error 表示一般错误,通常由服务器报告
parsererror 指示无法解析服务器返回的数据
timeout 表示请求在服务器响应前超时

errorMsg参数的值根据status而变化。当statuserror时,那么errorMsg将被设置为服务器响应的文本部分。因此,在这个例子中,来自服务器的响应是404 Not Found,因此errorMsg被设置为Not Found

statustimeout时,errorMsg的值也将为timeout。您可以使用timeout设置来指定请求超时之前的时间段,我在“设置超时和头”一节中对此进行了描述

statusparsererror时,那么errorMsg将包含问题的细节。当数据格式不正确或服务器为数据返回错误的 MIME 类型时,会出现此错误。(您可以使用dataType设置覆盖数据类型。)最后,当请求是abort时,状态和errorMsg值都将是abort

image 提示虽然我已经在文档中显示了statuserrorMsg值,但这通常对用户没有帮助,因为这些消息需要对 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属性指定的回调函数在由successerror属性指定的函数之后被调用。jQuery 将jqXHR对象和一个状态字符串传递给回调函数。状态字符串将被设置为表 15-5 中显示的值之一。

表 15-5 。Ajax 事件设置

环境 描述
abort 表示请求被中止(使用jqXHR对象)
error 表示一般错误,通常由服务器报告
notmodified 表示所请求的内容自上次请求以来没有被修改过(有关更多详细信息,请参见“忽略未修改的数据”一节)
parsererror 指示无法解析服务器返回的数据
success 指示请求成功完成
timeout 表示请求在服务器响应前超时

您可能想使用complete设置来指定一个可以处理请求所有结果的函数,但是这样做意味着您不能从 jQuery 处理数据和错误的方式中获益。更好的方法是使用successerror设置,并仔细组织公共函数的参数,如清单 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 请求,但是您可以将successerrorcompletebeforeStart属性设置为一个函数数组,当相应的事件被触发时,它们中的每一个都将被执行。清单 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 中看到成功和失败请求的结果。

9781430263883_Fig15-02.jpg

图 15-2 。使用上下文属性来指示 Ajax 请求的结果

image 提示你可以使用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 请求成功时要调用的函数

image 提示在 jQuery 1.9 之前,您可以对任何元素调用表中的方法,但是在 jQuery 1.9/2.0 中,您只能对document元素调用表中的方法,如本节中的示例所示。

这些方法用于注册处理函数,并且必须应用于document元素(正如我稍后演示的)。ajaxStartajaxStop方法不向处理函数传递任何参数,但是其他方法提供以下参数:

  • 描述事件的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 请求生成的消息。

9781430263883_Fig15-03.jpg

图 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,服务器将不会缓存请求的内容。默认情况下,scriptjsonp数据类型不会被缓存,但其他数据类型会被缓存。
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

image 注意请求一传到浏览器,计时器就开始计时,大多数浏览器对并发请求的数量都有限制。这意味着您冒着在请求开始之前就超时的风险。为了避免这种情况,您必须了解浏览器的局限性以及正在进行的任何其他 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,因此浏览器不会执行调用sliceappendTo方法的语句,直到请求完成并且结果被分配给elems变量(假设请求成功完成)。使用 Ajax 方法进行同步调用是一件奇怪的事情,我建议您考虑一下为什么您的 web 应用需要这样做。

我经常在查看麻烦的 Ajax 代码时使用这种技术作为快速测试——不能正确处理异步请求是如此常见的问题,以至于我开始用快速同步测试进行调试。如果代码有效,我知道要开始寻找关于数据何时从服务器到达的错误假设。

image 提示不要使用同步调用,因为你会发现进行异步调用很费力;我知道使用回调和确保不要对请求的结果做出假设可能会令人厌倦,但是花时间了解这种 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 中看到。

9781430263883_Fig15-04.jpg

图 15-4 。使用 ifModified 设置

image 注意这可能是一个有用的设置,但应该小心使用。如果您的请求是用户操作的结果(比如,按下按钮),那么用户按下按钮可能是因为之前的请求没有按预期的方式执行。假设您请求数据,但是success方法包含一个错误,不能正确地用内容更新文档;用户按下按钮试图让文档正确显示。通过不明智地使用ifModified设置,您可能会忽略用户操作,迫使用户采取更严肃的步骤来解决问题。

处理响应状态代码

属性 允许您响应 HTTP 响应中返回的不同状态代码。您可以使用这个特性作为successerror属性的替代或补充。清单 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 中,我定义了三个函数,并将它们与状态码200404302相关联。

传递给函数的参数取决于状态代码反映的是成功的请求还是错误。如果代码表示成功(比如200,那么参数与success回调函数的参数相同。对于失败状态代码,比如404代码,它表示无法找到请求的文件,参数与error回调函数相同。

注意,我还为302代码添加了一个地图。当服务器希望将您重定向到另一个 URL 时,这将被发送回浏览器。jQuery 会自动跟踪重定向,直到接收到一些内容或遇到错误。这意味着我的302代码的函数永远不会被调用。

image 提示只有在使用了ifModified设置的情况下,才会生成304代码,表示内容自上次请求后未被修改。否则,jQuery 会发送一个200代码。有关ifModified设置的信息,参见上一节“忽略未修改的数据”。

当我调试浏览器和服务器之间的交互时,我发现这个特性很有用,通常是为了找出 jQuery 为什么没有按照我想要的方式运行。当我这样做时,我使用statusCode设置来补充successerror设置,并将信息打印到控制台。

image 提示successerror回调函数在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设置,那么第二个函数参数将是undefineddataFilter函数的目的是返回过滤后的数据,在清单 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 中看到。

9781430263883_Fig15-05.jpg

图 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 方法。

image 提示数据类型并不总是与服务器返回的 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 中,我定义了timeoutglobalerrorconverters设置的默认值。一旦我调用了ajaxSetup方法,我只需要为那些我没有提供默认值或者我想改变其值的设置定义值。当发出大量具有相似配置的 Ajax 请求时,这可以减少代码重复。

image 提示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 的一个更广阔的视角。

image 提示我不打算在这一章中试图保留一个可行的非 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>

image 提示我在脚本中的很多地方动态地插入了元素。我不会让它们保持静态,而是让它们保持原样,这样我就可以专注于添加新功能。

这不是完全相同的文档:我通过添加一个style元素,而不是在单个选择上使用css方法,整理了级联样式表(CSS ) 的附加内容。你可以在图 16-1 中看到这个 HTML 文档是如何出现在浏览器中的,当然,第十一章分解了到目前为止我对文档所做的一系列修改。

9781430263883_Fig16-01.jpg

图 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 的中看到一个显示给用户的错误示例。

9781430263883_Fig16-02.jpg

图 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方法一样工作。除了对getgetJSON方法的调用之外,我还添加了一个数据模板,这样我就可以轻松地处理 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元素的选择,我将不会匹配任何元素(因为它们还没有被创建和添加到文档中),我的验证设置将会失败。

为了解决这个问题,我使用了whenthen方法,它们是我在第三十五章中描述的 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 文档的一部分。

验证插件还支持远程验证,用户输入的值被发送到服务器,规则在那里被应用。当您不想将验证规则发送到浏览器时,这很有用,因为这需要太多的数据(例如,您可以通过检查用户名是否已被使用来验证新帐户的用户名,这需要将所有帐户名发送到客户端进行本地验证)。

image 注意使用远程验证时需要注意,因为它会给服务器带来很大的负载。在这个例子中,每当用户改变一个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 所示。

9781430263883_Fig16-03.jpg

图 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 中的(为了清晰起见,我已经删除了其他元素)。

9781430263883_Fig16-04.jpg

图 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 请求期间浏览器是如何出现的。

9781430263883_Fig16-05.jpg

图 16-5 。表单提交期间的浏览器请求

处理服务器响应

剩下的就是对我从服务器上获得的数据做一些有用的事情。对于这一章,我将使用一个简单的表格。在本书的下一部分中,您将学习如何使用 jQuery UI 创建丰富的用户界面,我不想手工完成我可以用 UI 小部件更优雅地完成的工作。图 16-6 显示了最终的结果。

9781430263883_Fig16-06.jpg

图 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 请求中返回的数据。

image 提示在前面的例子中,我一直使用$("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函数中的显式延迟,并在请求中添加了dataFilterconverterssuccess设置。我使用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 中看到的。

9781430263883_Fig16-07.jpg

图 16-7 。显示选择错误

我正在显示一条一般的消息,但是高亮显示只应用于一个input元素,这会让用户感到困惑。为了解决这个问题,我删除了验证插件用于高亮显示的类,如下所示:

...
$(elem).removeClass("invalidElem");
...

这产生了图 16-8 中所示的效果。

9781430263883_Fig16-08.jpg

图 16-8 。从与错误相关联的元素中移除突出显示

摘要

在这一章中,我重构了这个例子,把这本书这一部分涉及的主题和特性结合在一起。我广泛使用了 Ajax(使用速记和低级方法),应用了一对数据模板,并使用验证插件在本地和远程检查值(并手动显示错误)。在本书的下一部分,我将转向 jQuery UI,下一次我重构示例文档时,它将有一个非常不同的外观。

十七、设置 jQuery UI

下载和安装 jQuery UI 比其他 JavaScript 库更复杂。这并不麻烦,但需要一些解释,我在这一章中提供了。您只需要为这本书的开发做好准备,但是我也包括了如何安装适合生产部署的最小化文件以及如何通过内容分发网络(CDN)使用 jQuery UI 的细节。

image 注意正如我在第一章中所解释的,jQuery UI API 随着 1.10 版本的发布而发生了变化,我将在接下来的章节中重点介绍这些变化。

获取 jQuery UI

jQuery UI 中有四个主要的功能区域,您可以创建一个自定义下载来包含和配置每一个区域。在本书的这一部分,我向您展示了 jQuery UI 的所有特性,但是对于真正的 web 应用,您可以省略不需要的部分,并创建一个较小的库供浏览器下载。

image 提示 jQuery UI 并不是唯一基于 jQuery 的 UI 工具包,尽管它是目前最流行的。另一个选择是 jQuery Tools,它是开源的,可以从http://flowplayer.org/tools下载,没有任何许可或限制。还有一些商业上的替代品,比如 jQWidgets ( www.jqwidgets.com)和 Wijmo ( http://wijmo.com)。当然,还有 jQuery Mobile,我在本书的第五部分对此进行了描述。

决定主题

在构建自定义 jQuery UI 库之前,您需要选择一个主题。jQuery UI 是无限可配置的,您可以更改您使用的每个特性的外观。事实上,有这么多的选择,它可以有些不知所措。jQuery UI web 站点包括一个用于创建定制主题的工具,但是还有一个预定义主题库,您可以选择这些主题来简化工作。

首先,转到http://jqueryui.com并点击Themes按钮。这将加载主题滚轮页面,该页面由一个 jQuery UI 小部件的显示和一个左侧面板组成,该面板允许您配置主题设置,如图图 17-1 所示。

9781430263883_Fig17-01.jpg

图 17-1 。jQuery UI 网站主题页面

如果您需要遵循某种视觉样式来使 jQuery UI 适应站点或应用的其余部分,请使用 Roll Your Own 选项卡(默认情况下选中)。您可以更改 jQuery UI 使用的主题的各个方面。

预定义的主题在图库选项卡上可用。当我写这篇文章时,画廊里有 24 个主题可供选择,它们从低调和微妙到明亮和花哨。当你点击每个图库主题时,页面其余部分的窗口小部件会更新,向你展示你的应用的外观,如图 17-2 所示。

9781430263883_Fig17-02.jpg

图 17-2 。展示阳光主题的画廊

jQuery UI 的默认主题叫做 UI 亮度,但是它没有足够的对比度来很好地显示在图书页面上,所以我将使用阳光主题,它显示得稍微好一点。目前你不需要对主题做任何事情,除了记住你想用哪一个。这些主题打印出来时看起来不太好,但它们在屏幕上有更好的外观,我建议你浏览列表,直到找到你喜欢的。

image 提示你不必选择我将使用的相同主题,但如果你选择不同的主题,你显然会得到与我不同的结果。

创建 jQuery UI 自定义下载

既然已经有了主题,就可以创建 jQuery UI 下载了。点击页面顶部的Download按钮,进入构建您的下载页面。

第一步是选择想要下载的 jQuery UI 版本。当我写这篇文章时,最新的版本是1.10.3,这也是我将在本书中使用的版本。

除了指定 jQuery UI 版本之外,该页面还有一个 jQuery UI 组件列表,分为四个功能组: UI 核心交互小部件效果

通过只选择项目所需的功能,您可以创建一组较小的文件供浏览器下载。我认为这是一个不错的想法,但我倾向于不使用它,因为我更喜欢通过使用 CDN 来减少交付 jQuery UI 所需的带宽,我在“通过内容分发网络使用 jQuery UI”一节中向您展示了如何做到这一点

对于这一章,您将需要所有的组件,所以确保所有四个选项都被选中。

image 提示列表中的一些组件依赖于其他组件,但是在构建自定义的 jQuery UI 库时,你不必担心这一点。当您启用一个组件时,它所依赖的任何其他组件也会被加载。

下一步是选择你想要的主题。这个选择器在页面的底部,就在下载按钮的上方,如图图 17-3 所示。

9781430263883_Fig17-03.jpg

图 17-3 。选择主题

一旦您选择了所有的组件、您想要的主题和稳定版本,单击Download按钮下载定制的 jQuery UI 库。

安装用于开发的 jQuery UI

jQuery UI 下载是一个 zip 文件,包含开发和生产所需的所有文件。对于本书,您将使用包含未统一源代码的开发文件。如果您遇到问题,这使得查看 jQuery UI 的内部变得很容易。您需要将以下内容复制到包含示例文件的文件夹中:

  • js\jquery-ui-1.10.3.custom.js文件
  • themes\sunny\jquery-ui-1.10.3.custom.css文件
  • themes\sunny\images文件夹

您会注意到在uithemes文件夹中有针对单个组件和特性的 JavaScript 和层叠样式表(CSS)文件。您不需要使用它们,但是如果您只需要使用有限的 jQuery UI 特性,它们会很有帮助。

image 提示JavaScript 和 CSS 文件的名称包括下载的版本号。对我来说,这是 1.10.3 版本。jQuery UI 正在积极开发中,您可能已经下载了更高版本。如果是这样,您需要在 HTML 示例中更改对 jQuery UI 文件的引用。

向 HTML 文档添加 jQuery UI

剩下的就是将 jQuery UI 添加到一个示例 HTML 文档中,这是通过引用我在上一节中列出的 JavaScript 和 CSS 文件的scriptlink元素来完成的,如清单 17-1 所示。

清单 17-1 。向 HTML 文档添加 jQuery UI

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.8.16.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.8.16.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {
            $("a").button();
        });
    </script>
</head>
<body>
    <a href="http://apress.com">VisitApress</a>
</body>
</html>

image 提示你不需要直接查阅images目录。只要图像目录和 CSS 文件在同一个地方,jQuery UI 就能够找到它需要的资源。

image 提示 jQuery UI 依赖于 jQuery。为了使用 jQuery UI,您必须将 jQuery 添加到文档中。

清单中显示的文档包括一个简单的测试,允许您检查 jQuery UI 是否已经正确添加。如果你在浏览器中查看这个,你应该会看到一个类似于图 17-4 中的按钮。不用担心对清单中的script元素中的button方法的调用。我会在第十八章的中解释这是做什么的以及它是如何工作的。

9781430263883_Fig17-04.jpg

图 17-4 。检查 jQuery UI 是否已经正确添加到文档中

如果你没有正确指定这两个文件的路径,那么你会看到一个简单的a元素,如图 17-5 中的所示。

9781430263883_Fig17-05.jpg

图 17-5 。识别问题将 jQuery UI 导入到文档中

为生产安装 jQuery UI

当您完成 web 应用的开发并准备好部署它时,您可以使用 jQuery UI 下载中包含的精简文件。这个文件更小,但是为了调试的目的更难阅读。要使用生产文件,您必须将以下内容复制到 web 服务器目录中:

  • js\jquery-ui-1.10.3.custom.min.js文件
  • themes\sunny\jquery-ui-1.10.3.custom.css文件
  • themes\sunny\images文件夹

images目录和 CSS 文件与开发版相同;只有 JavaScript 文件发生了变化。

通过内容分发网络使用 jQuery UI

我在第五章的中提到了使用 CDN 进行 jQuery。如果这是一种吸引你的方法,你会很高兴地得知你可以用 jQuery UI 做同样的事情。Google 和微软都将 jQuery UI 文件放在他们的 cdn 上。对于这个例子,我将使用 Microsoft 服务,因为它托管标准主题以及 jQuery UI JavaScript。

要使用 CDN,你只需要你想要的文件的 URL。对于微软服务,请转到www.asp.net/ajaxlibrary/cdn.ashx开始使用。如果您向下滚动页面,您将看到一个 jQuery UI 发布的部分,按版本细分。单击您正在使用的版本的链接。您将看到 jQuery UI 库文件的常规和最小化版本的 URL。对于我写这篇文章时的最新版本,最小化文件的 URL 如下:

http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/jquery-ui.min.js

页面的其余部分显示了每个预定义的 jQuery UI 主题,主题 CSS 的 URL 显示在下面。阳光主题的网址如下:

http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/sunny/jquery-ui.css

要在 CDN 上使用这些文件,只需将 URL 放在引用本地 jQuery UI 文件的scriptlink元素中,如清单 17-2 所示。

清单 17-2 。通过 CDN 使用 jQuery UI

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="[`ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/jquery-ui.min.js`](http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/jquery-ui.min.js)"
        type="text/javascript"></script>
    <link
    href="[`ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/sunny/jquery-ui.css`](http://ajax.aspnetcdn.com/ajax/jquery.ui/1.10.3/themes/sunny/jquery-ui.css)"
        rel="stylesheet" />
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $("a").button();
        });
    </script>
</head>
<body>
    <a href="http://apress.com">VisitApress</a>
</body>
</html>

同样,你可以通过加载文档并查看浏览器是否显示一个类似于图 17-4 中的按钮来判断你是否有正确的 URL。

摘要

在本章中,我向您展示了创建和准备 jQuery UI 下载所需的步骤。在包含的特性和 jQuery UI 赋予 web 应用的默认外观方面有很大的灵活性。我特别喜欢 ThemeRoller 应用。这是一种创建完全定制的主题以适应现有视觉方案的优雅方式,非常适合将 jQuery UI 添加到公司品牌的站点。在下一章中,您将开始研究不同的 jQuery UI 特性,从最流行的功能领域开始:小部件。

十八、使用按钮、进度条和滑块小部件

现在您已经配置、下载并安装了 jQuery UI,您可以开始查看它包含的小部件了。这些是 jQuery UI 的主要功能块,尽管还有其他特性(比如效果,我在第三十五章中描述过),jQuery UI 还是以小部件而闻名。

在这一章中,我描述了三个最简单的部件:按钮、进度条和滑块。所有 jQuery UI 小部件都有共同的特征:设置、方法和事件。掌握一个小部件为使用所有这些部件提供了坚实的基础,所以我在本章开始时花了一些时间来提供整体背景。

很难将所有的小部件都绑定到花店示例中,因此您会发现本书这一部分中的许多示例都是小型的、自包含的 HTML 文档,它们演示了一个小部件。当我重构它以包含 jQuery UI 时,我回到了第二十六章中的花店示例。表 18-1 对本章进行了总结。

表 18-1 。章节总结

问题 解决办法 列表
创建 jQuery UI 按钮 选择一个元素并使用button方法 one
配置按钮元素 将地图对象传递给button方法或使用option方法 2, 3
在 jQuery UI 按钮中使用图标 使用icons设置 four
在 jQuery UI 按钮中使用自定义图像 将按钮的内容设置为一个img元素 five
删除 jQuery UI 按钮小部件 使用destroy方法 six
启用或禁用 jQuery UI 按钮 使用enabledisable方法 seven
刷新 jQuery UI 按钮的状态,以反映对基础元素的编程更改 使用refresh方法 eight
响应正在创建的 jQuery UI 按钮 create事件指定一个函数 nine
从不同种类的元素创建统一的按钮 inputbuttona元素创建 jQuery UI 按钮 Ten
创建切换按钮 从复选框创建 jQuery UI Eleven
创建按钮集 使用buttonset方法 12, 13
创建 jQuery UI 进度条 使用progressbar方法 14, 15
获取或设置向用户显示的进度 使用value方法 Sixteen
响应进度条中的更改 指定createchangecomplete事件的功能 Seventeen
创建 jQuery UI 滑块 使用slider方法 Eighteen
更改 jQuery UI 滑块的方向 使用orientation设置 19, 20
当用户单击滑块时,动画显示手柄的移动 使用animate设置 Twenty-one
创建一个 jQuery UI 滑块,允许用户指定值的范围 使用rangevalues设置 Twenty-two
以编程方式控制 jQuery UI 滑块 使用valuevalues方法 Twenty-three
响应滑块手柄位置的变化 处理startstopchangeslide事件 Twenty-four

自上一版以来,JQUERY UI 发生了变化

从 jQuery UI 1.10 开始,progress 小部件可以用来显示不确定的任务。有关详细信息和示例,请参见“使用 jQuery UI 进度条”一节。

使用 jQuery UI 按钮

我看到的第一个小部件很好地介绍了 jQuery UI 的世界。button 小部件很简单,但是对 HTML 文档有一种转换效果。按钮小部件将 jQuery UI 主题应用于buttona元素。这意味着元素的大小、形状、字体和颜色被转换以匹配我在第十七章中创建自定义 jQuery UI 下载时选择的主题。应用 jQuery UI 小部件很简单,如清单 18-1 所示。

清单 18-1 。一个简单的 HTML 文档

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <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 tmplData = $("#flowerTmpl")
                        .template({ flowers: data }).filter("*");
                    tmplData.slice(0, 3).appendTo("#row1");
                    tmplData.slice(3).appendTo("#row2");
                }
            });

            $("button").button();
        });
    </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>

为了应用 button 小部件,我使用 jQuery 选择我想要转换的元素,并调用button方法;jQuery UI 负责剩下的工作。你可以在图 18-1 中看到效果。

9781430263883_Fig18-01.jpg

图 18-1 。应用按钮小部件

image 提示注意,我将button方法应用于 jQuery 选择对象。jQuery 和 jQuery UI 之间的集成非常紧密,这意味着使用 jQuery UI 通常是我在本书前面向您展示的核心 jQuery 技术的自然扩展。

像所有 jQuery UI 小部件一样,图中显示的按钮是应用于现有 HTML 元素的一系列 CSS 样式。button方法像这样转换一个元素

<button type="submit">Place Order</button>

对此

<button type="submit" class="ui-button ui-widget ui-state-default ui-corner-all
    ui-button-text-only" role="button" aria-disabled="false">
        <span class="ui-button-text">Place Order</span>
</button>

这是一个很好的方法,因为它围绕标准的 HTML 元素工作,这意味着在创建 HTML 内容时,您不必为 jQuery UI 做任何特殊的准备。

配置按钮

jQuery UI 按钮小部件可以通过设置属性进行配置,允许控制创建按钮的方式。表 18-2 描述了这些属性。

表 18-2 。按钮小工具的设置属性

财产 描述
disabled 获取或设置按钮的禁用状态。true值表示按钮被禁用。jQuery UI 不考虑底层 HTML 元素的状态。
text 获取或设置按钮是否显示文本。如果icons属性为false,则忽略该设置。
icons 获取或设置按钮是否显示图标。
label 获取或设置按钮显示的文本。

这些设置有两种应用方式。第一个是在调用button方法时使用 map 对象,如清单 18-2 中所强调的。

清单 18-2 。使用地图对象配置按钮微件

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

        $("button").button({
            label: "Place Your Order",
            disabled: true
        });

        $("button").button("option", "disabled", false);
    });
</script>
...

我已经用label属性设置了按钮显示的文本,并使用disabled属性禁用了按钮。使用地图对象是定义小部件初始配置的方法,并遵循你最近在第十五章中看到的配置 Ajax 请求的风格。

清单 18-2 还展示了在创建小部件后改变设置属性值的技术,如下所示:

...
$("button").button("option", "disabled", false);
...

我再次调用button方法,但是这次有三个参数。第一个参数是字符串option,它告诉 jQuery UI 我想更改一个设置。第二个参数是我想要更改的设置,第三个参数是设置的新值。这条语句将disabled设置为false,启用按钮并更改我在创建小部件时用地图对象设置的值。

您可以将这些技术结合起来,这样就可以用第一个参数option和第二个参数 map 对象来调用button方法。这允许你一次改变多个设置,如清单 18-3 所示。

清单 18-3 。对地图对象使用选项参数

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

        $("button").button();

        $("button").button("option", {
            label: "Place Your Order",
            disabled: false
        });

        console.log("Disabled: " + $("button").button("option", "disabled"));
    });
</script>
...

你也可以使用同样的技术让读取一个设置的值。在这种情况下,您调用只有两个参数的button方法。第一个是字符串option,第二个是要读取其值的设置的名称,如清单中的以下语句所示:

...
console.log("Disabled: " + $("button").button("option", "disabled"));
...

以这种方式使用时,button方法返回当前设置值。示例语句读取disabled设置的值,并将其写入控制台,产生以下输出:

Disabled: false

在按钮中使用 jQuery UI 图标

jQuery UI 主题包括一组可以用于任何目的的图标,包括在按钮中显示它们。清单 18-4 显示了图标在 jQuery UI 按钮中的使用。

清单 18-4 。在按钮中显示图标

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

        $("button").button({
            icons: {
                primary: "ui-icon-star",
                secondary: "ui-icon-circle-arrow-e"
            }
        });
    });
</script>
...

属性指定将显示哪些图标。按钮部件有两个图标位置。primary图标显示在文本的左侧,而secondary图标显示在文本的右侧,两者都由地图对象指定。您可以省略任一属性,只在其中一个位置显示图标。图标本身很小,正如你在图 18-2 中看到的。

9781430263883_Fig18-02.jpg

图 18-2 。在按钮中显示图标

图标是使用 jQuery UI CSS 文件中定义的类来指定的。有 173 种不同的图标可用,太多了,无法在此列出。找出你想要的图标名称的最简单的方法是进入http://jqueryui.com,选择主题页面并向下滚动页面。你会看到所有的图标都列在一个网格中,在每个图标上移动鼠标按钮会显示图标的类名,如图 18-3 所示。

9781430263883_Fig18-03.jpg

图 18-3 。jQuery UI 图标网格

image 提示在网页上弹出的名字有一个前导句点,必须省略才能与图标设置一起使用。所以,举例来说,如果鼠标悬停在网格中的第一个图标上,就会弹出.ui-icon-caret-1-n。要将此图标与按钮一起使用,请将 primary 或 secondary 属性设置为ui-icon-caret-1-n

使用自定义图像

我觉得 jQuery UI 图标没什么用,因为它们通常太小,不符合我的需求。幸运的是,有两种替代技术可以在 jQuery UI 按钮中显示自定义图像。

第一种方法是在打算应用 jQuery UI 按钮小部件的button元素中插入一个img元素。jQuery UI 按钮小部件尊重底层button元素的内容,并且——只要您使用透明背景的图像——您就不必担心使图像与主题相匹配。清单 18-5 给出了一个简单的演示。

清单 18-5 。使用带有 jQuery UI 按钮的自定义图像

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

        $("button")
            .text("")
            .append("<img src=rightarrows.png width=100 height=30 />")
            .button();
    });
</script>
...

因为我既不想要文本也不想要 jQuery UI 图标,所以我使用 jQuery text方法将内容设置为空字符串。然后我使用append方法将一个img元素插入到button元素中,最后调用button方法来应用 jQuery UI。你可以在图 18-4 中看到结果。

9781430263883_Fig18-04.jpg

图 18-4 。在按钮中显示自定图像

使用按钮方法

jQuery UI 小部件还定义了在小部件创建后用来控制它的方法。这些方法有点奇怪,因为您调用相同的 JavaScript 方法,但是传入不同的参数值来改变 jQuery UI 行为。然而,这些仍然被 jQuery UI 团队称为方法,我也这样称呼它们。表 18-3 显示了你可以在按钮部件上使用的不同的 jQuery UI 方法以及每种方法的效果。

表 18-3 。按钮方法

方法 描述
button("destroy") 将 HTML 元素返回到其原始状态
button("disable") 禁用了按钮
button("enable") 启用按钮
button("option") 设置一个或多个选项;请参见“配置按钮”一节
button("refresh") 刷新按钮;请参见“刷新 jQuery UI 按钮的状态”一节

移除小组件

destroy方法从 HTML button元素中移除 jQuery UI 按钮小部件,将其恢复到原始状态,如清单 18-6 所示。

清单 18-6 。使用销毁方法

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

        $("button").button().click(function (e) {
            $("button").button("destroy");
            e.preventDefault();
        });
    });
</script>
...

在清单 18-6 的中,我使用标准的 jQuery click方法为button元素注册了一个处理函数。这是我在第九章中演示的处理事件的技术,不需要调整来支持 jQuery UI。清单中的click处理函数意味着点击按钮从按钮元素中移除 jQuery UI 小部件,如图 18-5 所示。

9781430263883_Fig18-05.jpg

图 18-5 。销毁 jQuery UI 按钮小部件

启用和禁用按钮

enabledisable方法改变 jQuery UI 按钮的状态,如清单 18-7 所示。

清单 18-7 。启用和禁用按钮

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

        $("<span>Enabled:<span><input type=checkbox checked />").prependTo("#buttonDiv");
        $(":checkbox").change(function (e) {
            $("button").button(
                $(":checked").length == 1 ? "enable" : "disable"
            )
        });

        $("button").button();
    });
</script>
...

我在文档中插入了一个复选框,并使用change方法注册了一个函数,当复选框被选中或未选中时,将调用这个函数。我调用enabledisable方法来改变按钮的状态以匹配复选框。你可以在图 18-6 中看到效果。

9781430263883_Fig18-06.jpg

图 18-6 。启用和禁用 jQuery UI 按钮

刷新 jQuery UI 按钮的状态

refresh方法更新 jQuery UI 按钮小部件的状态,以反映底层 HTML 元素的变化。这在你修改代码中其他地方的元素时会很有用,如清单 18-8 所示。

清单 18-8 。刷新 jQuery UI 按钮

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

        $("<span>Enabled:<span><input type=checkbox checked />").prependTo("#buttonDiv");
        $(":checkbox").change(function (e) {
            var buttons = $("button");
            if ($(":checked").length == 1) {
                buttons.removeAttr("disabled");
            } else {
                buttons.attr("disabled", "disabled");
            }
            buttons.button("refresh");
        });

        $("button").button();
    });
</script>
...

在这个例子中,我使用复选框来触发从支撑按钮小部件的 HTML button元素中添加和删除disabled属性。jQuery UI 不会自动检测到这种变化,所以我调用了refresh方法来使一切恢复同步。

image 提示您可能想知道为什么我不直接使用 jQuery UI enabledisable方法,但是清单 18-8 中的场景出奇地常见,因为 jQuery UI 经常在开发过程的后期应用于 HTML 内容,经常用于改进现有的 web 应用。在这种情况下,HTML 元素将由 jQuery UI 用户之前的代码生成和操作,并且不知道将使用按钮小部件,因此能够更新小部件的状态以反映底层 HTML 元素的状态是一个重要的特性。

使用按钮事件

除了底层 HTML 元素的事件之外,jQuery UI 小部件还定义了事件。button小部件定义了一个名为create的事件,当您创建一个 jQuery UI 按钮小部件时会触发该事件。jQuery UI 事件的处理程序是通过将一个 JavaScript map 对象传递给小部件的 jQuery UI 方法来定义的,在本例中是button,如清单 18-9 所示。

清单 18-9 。使用 jQuery UI 按钮创建事件

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

        $("button").button({
            create: function (e) {
                $(e.target).click(function (e) {
                    alert("Button was pressed");
                    e.preventDefault();
                })
            }
        });
    });
</script>
...

在清单 18-9 中,我使用create事件来设置一个函数来响应button上的click事件。我觉得create事件没什么用处,通常我发现任何可以响应这个事件的事情都可以用一种更符合更广泛的 jQuery 方法的方式来完成。

创建不同类型的按钮

jQuery UI 按钮小部件对它所应用的元素种类很敏感。当您对button元素、或者a元素、或者类型被设置为submitresetbuttoninput元素调用button方法时,会创建一个常规按钮的基本行为。清单 18-10 显示了所有这些被转换成 jQuery UI 按钮的元素。

清单 18-10 。创建标准按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-1.7.js" type="text/javascript"></script>
    <script src="jquery-ui-1.8.16.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.8.16.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {
            $(".jqButton").click(function(e) {
               e.preventDefault();
               $(this).button();
            });
        });
    </script>
</head>
<body>
    <form>
        <input class="jqButton" type="submit" id="inputSubmit" value="Submit">
        <input class="jqButton" type="reset" id="inputReset" value="Reset">
        <input class="jqButton" type="button" id="inputButton" value="Input Button">
        <button class="jqButton">Button Element</button>
        <a class="jqButton" href="[`apress.com`](http://apress.com)">A Element</a>
    </form>
</body>
</html>

我已经定义了我所描述的每一种 HTML 元素。我使用了click方法,以便每个元素在被单击时都被转换成一个 jQuery UI 按钮小部件。你可以在图 18-7 中看到这种转变。

9781430263883_Fig18-07.jpg

图 18-7 。创建标准 jQuery UI 按钮

创建切换按钮

如果在类型设置为checkboxinput元素上调用button方法,就会得到一个切换按钮小部件。当您单击切换按钮时,它会根据底层 HTML 元素的选中和未选中状态打开或关闭。清单 18-11 提供了一个演示。

清单 18-11 。将 jQuery UI 应用于复选框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <script type="text/javascript">
        $(document).ready(function () {
            $(".jqButton").button();
        });
    </script>
</head>
<body>
    <form>
        <input class="jqButton" type="checkbox" id="toggle">
        <label for="toggle">Toggle Me</label>
    </form>
</body>
</html>

要从复选框创建 jQuery UI 按钮,必须有一个input元素和一个匹配的label元素,如清单所示。jQuery UI 创建了一个按钮,它与基本按钮具有相同的外观,但是在单击时会切换其状态。你可以在图 18-8 中看到效果。

9781430263883_Fig18-08.jpg

图 18-8 。从复选框创建切换按钮

请记住,jQuery UI 不会改变底层的 HTML 元素,所以当复选框包含在表单中时,浏览器仍然会以同样的方式处理它。使用checked属性反映状态的变化,就像没有 jQuery UI 一样。

创建按钮集

您可以使用buttonset方法从单选按钮元素创建 jQuery UI 按钮,如清单 18-12 中的所示。

清单 18-12 。创建按钮集

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <script type="text/javascript">
        $(document).ready(function () {
            $("#radioDiv").buttonset();
        });
    </script>
</head>
<body>
    <form>
        <div id="radioDiv">
            <input type="radio" name="flower" id="rose" checked />
            <label for="rose">Rose</label>
            <input type="radio" name="flower" id="lily"/><label for="lily">Lily</label>
            <input type="radio" name="flower" id="iris"/><label for="iris">Iris</label>
        </div>
    </form>
</body>
</html>

注意,我选择了包含单选按钮的div元素,以便调用buttonset方法:您不需要在单个input元素上调用button方法。你可以在图 18-9 的中看到buttonset方法的效果。

9781430263883_Fig18-09.jpg

图 18-9 。创建按钮集

最多只能选择按钮集中的一个按钮,这允许您以与其他 jQuery UI 按钮在视觉上一致的方式为用户提供一组固定的选项。请注意,jQuery UI 通过将不同的样式应用到按钮相遇的边缘,强调了一组按钮之间的关系。这在图 18-10 中显示得更清楚。

9781430263883_Fig18-10.jpg

图 18-10 。按钮集的 jQuery UI 样式

从常规按钮创建按钮集

您可以在任何可以与常规的button方法一起使用的元素上使用buttonset方法。这具有应用一组单选按钮的样式但是而不是行为的效果,以便每个按钮单独工作。清单 18-13 展示了buttonset方法的这种用法。

清单 18-13 。从常规按钮创建按钮集

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <script type="text/javascript">
        $(document).ready(function () {
            $("#radioDiv").buttonset();
        });
    </script>
</head>
<body>
    <form>
        <div id="radioDiv">
            <input type="submit" value="Submit"/>
            <input type="reset" value="Reset"/>
            <input type="button" value="Press Me"/>
            <a href="[`apress.com`](http://apress.com)">Visit Apress</a>
        </div>
    </form>
</body>
</html>

div容器中任何合适的元素都将被转换成一个按钮,相邻的边缘将被设计成单选按钮的样式,如图 18-11 中的所示。

9781430263883_Fig18-11.jpg

图 18-11 。从常规按钮创建按钮集

image 提示使用这种技巧时要小心。这可能会让用户感到困惑,尤其是当您在同一文档或 web 应用的其他地方使用单选按钮时。

使用 jQuery UI 进度条

现在我已经使用按钮探索了 jQuery UI 小部件的基本结构,接下来我将看看 jQuery UI 支持的其他小部件,从进度条开始。

进度条允许您向用户显示完成任务的进度。进度条被设计用来显示确定性任务,在这里你可以给用户一个精确的指示,以百分比表示你完成任务的进度,以及不确定性任务,在这里进度的百分比目前是未知的。

image 提示jQuery UI 在 1.10 版本中增加了显示不确定任务的支持。有关此功能的详细信息,请参见“创建不确定的进度条”一节。

显示有用的进度信息

web 应用中应该如何使用窗口小部件没有规则,但是用户对进度条等控件的期望是由 Windows 和 Mac OS 等操作系统设定的标准决定的。为了帮助用户理解你的进度条,有一些规则要遵循。

首先,只增加进度。当任务的步骤比最初预期的多时,不要试图降低进度。进度条显示任务已完成的百分比,而不是剩余时间的估计值。如果一项任务有不同的可能路径,那么显示最悲观的进度。与其让用户困惑,不如在进程中取得巨大的进步。

第二,不要在进度条上循环不止一次。如果您有足够的信息向用户显示相当准确的完成信息,那么您应该使用不确定的进度指示器。当进度接近 100%时,用户希望任务完成。如果进度条随后重置,并开始再次建立,你只是混淆了用户,使进度条的使用毫无意义。

创建进度条

你通过选择一个div元素并调用progressbar方法来创建一个进度条,如清单 18-14 所示。

清单 18-14 。创建进度条

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <script type="text/javascript">
        $(document).ready(function () {
            $("#progressDiv").progressbar({
                value: 21
            });
        });
    </script>
</head>
<body>
    <div id="progressDiv"></div>
</body>
</html>

该文档包含一个div元素,其idprogressDiv。要创建一个进度条,我必须使用一个空的div元素——如果div元素有任何内容,它将影响进度条的布局。我使用 jQuery 选择progressDiv元素并调用 jQuery UI progressbar方法,传入一个 map 对象来提供初始配置。进度条支持三种设置,我已经在表 18-4 中描述过。

表 18-4 。进度条小工具的设置

环境 描述
disabled 如果true,进度条将被禁用。默认值为false
value 设置向用户显示的完成百分比。默认值为零。将属性设置为false以显示不确定的进度条,如“创建不确定的进度条”一节所述
max 设置进度条将显示的最大值。默认为100

在这个例子中,我指定了一个初始值 21(由于我没有改变max设置的值,这个值相当于 21%),你可以在图 18-12 中看到这个效果。

9781430263883_Fig18-12.jpg

图 18-12 。创建进度条

创建不确定的进度条

从 jQuery UI 1.10 开始,jQuery UI 进度条小部件支持显示不确定任务的进度,这是通过将value配置属性设置为false而不是指定一个数值来配置的,如清单 18-15 所示。

清单 18-15 。创建不确定的进度条

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <script type="text/javascript">
        $(document).ready(function () {
            $("#progressDiv").progressbar({
                value: false
            });
        });
    </script>
</head>
<body>
    <div id="progressDiv"></div>
</body>
</html>

jQuery UI 显示一个不确定的进度,一个简单的动画应用于整个工具条。你需要运行这个例子来看看动画的效果,但是图 18-13 显示的是一帧。

9781430263883_Fig18-13.jpg

图 18-13 。创建不确定的进度条

使用进度条方法

进度条小部件定义了许多方法,这些方法与按钮的样式相同。换句话说,您调用 JavaScript progressbar方法,第一个参数指定您想要的 jQuery UI 小部件方法。表 18-5 描述了可用的方法。

表 18-5 。进度条方法

方法 描述
progressbar("destroy") div元素返回到其原始状态
progressbar("disable") 禁用了进度条
progressbar("enable") 启用进度条
progressbar("option") 设置一个或多个选项;有关配置 jQuery UI 小部件的详细信息,请参见“配置按钮”一节
progressbar("value", value) 获取和设置进度栏显示的值,并在不确定进度栏和确定进度栏之间切换

这些方法中的大部分都以与按钮部件相同的方式工作,所以我不打算再次演示它们。例外是value方法,它允许您获取和设置进度条显示的值,并在确定和不确定状态之间切换。清单 18-16 展示了价值方法的使用。

清单 18-16 。使用进度条值方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
     <script type="text/javascript">
         $(document).ready(function () {

             $("#progressDiv").progressbar({
                 value: 21
             });

             $("button").click(function (e) {
                 var divElem = $("#progressDiv");
                 if (this.id == "mode") {
                     divElem.progressbar("value", false);
                 } else {
                     var currentProgress = divElem.progressbar("value");
                     if (!currentProgress) {
                         divElem.progressbar("value", 21);
                     } else {
                         divElem.progressbar("value",
                             this.id == "decr" ? currentProgress - 10 :
                                                     currentProgress + 10)
                     }
                 }
             });
         });
    </script>
</head>
<body>
    <div id="progressDiv"></div>
    <button id="decr">Decrease</button>
    <button id="incr">Increase</button>
    <button id="mode">Indeterminate</button>
</body>
</html>

我在这个例子中添加了button元素。我用它们来增加或减少进度条显示的值,并在确定和不确定模式之间切换。每按一次DecreaseIncrease按钮,数值变化 10%,如图 18-14 中所示。

9781430263883_Fig18-14.jpg

图 18-14 。使用数值方法更改显示的进度

Indeterminate按钮调用value方法将进度条切换到中间模式。点击其他按钮将设置一个数字value,这将使进度条回到确定的模式,如图 18-15 中的所示。

9781430263883_Fig18-15.jpg

图 18-15 。更改进度条的模式

使用进度条事件

jQuery UI 进度条小部件定义了三个事件,如表 18-6 中所述。

表 18-6 。进度条事件

事件 描述
create 创建进度条时触发
change 当进度栏的值改变时触发
complete 当进度条的值设置为 100 时触发

清单 18-17 显示了正在使用的事件。

清单 18-17 。使用进度条事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
     <script type="text/javascript">
         $(document).ready(function () {

             $("button").button();

             $("#progressDiv").progressbar({
                 value: 21,
                 create: function (e) {
                     $("#progVal").text($("#progressDiv").progressbar("value"));
                 },
                 complete: function (e) {
                     $("#incr").button("disable")
                 },
                 change: function (e) {
                     var currentValue = $("#progressDiv").progressbar("value");
                     if (!currentValue) {
                         $("#progWrapper").hide();
                     } else {
                         if ($(this).progressbar("value") < 100) {
                             $("#incr").button("enable")
                         }
                         $("#progVal").text(currentValue);
                         $("#progWrapper").show();
                     }
                 }
             });

             $("button").click(function (e) {
                 var divElem = $("#progressDiv");
                 if (this.id == "mode") {
                     divElem.progressbar("value", false);
                 } else {
                     var currentProgress = divElem.progressbar("value");
                     if (!currentProgress) {
                         divElem.progressbar("value", 21);
                     } else {
                         divElem.progressbar("value",
                             this.id == "decr" ? currentProgress - 10 :
                                                     currentProgress + 10)
                     }
                 }
             });
         });
    </script>
</head>
<body>
    <div id="progressDiv"></div>
    <button id="decr">Decrease</button>
    <button id="incr">Increase</button>
    <button id="mode">Indeterminate</button>
    <span id="progWrapper">Progress: <span id="progVal"></span>%</span>
</body>
</html>

在清单 18-17 中,当进度条处于确定模式时,我使用span元素来显示数字进度值。我使用create事件来显示初始值,该值是使用value配置属性设置的。

image 提示注意,我已经为进度条的设置和事件使用了相同的地图对象。这不是必需的,但是它允许我在一次方法调用中创建和配置一个小部件。

我使用complete事件在进度到达100%时禁用Increase按钮,并使用change事件确保按钮对其他值启用,显示当前值并在确定和不确定模式之间切换。你可以在图 18-16 中看到效果。

9781430263883_Fig18-16.jpg

图 18-16 。响应进度条事件

image 提示在使用事件时,有一些事情需要记住。首先,每当value被设置为 100 或更大时,就会触发complete事件。例如,这意味着如果您重复将该值设置为 100,该事件可能会触发多次。其次,changecomplete事件都是为 100 或更大的值触发的,所以当您完成进度更新时,您必须能够处理这两个事件。

使用 jQuery UI 滑块

顾名思义,滑块小部件在 HTML 文档中的一个元素外创建一个滑块,并允许用户选择一个值,而不必在input元素中输入文本。使用slider方法将 slider 小部件应用于 HTML 元素,如清单 18-18 所示。

清单 18-18 。创建滑块

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <style>
        #slider { margin: 10px; }
    </style>
     <script type="text/javascript">
         $(document).ready(function () {
             $("#slider").slider();
         });
    </script>
</head>
<body>
    <div id="slider"></div>
</body>
</html>

滑块的主题与其他 jQuery UI 小部件一致,并允许用户使用鼠标或箭头键来上下移动滑块手柄。你可以在图 18-17 中看到基本滑块的样子。(注意,在这个例子中,我为支撑滑块的元素定义了一个 CSS 样式——如果没有这个样式,滑块将紧靠其父元素的边缘显示。)

9781430263883_Fig18-17.jpg

图 18-17 。一个基本的 jQuery UI 滑块

配置滑块

对于所有的 jQuery UI 小部件,slider 小部件定义了许多设置,您可以使用这些设置来配置滑块的外观和行为。表 18-7 描述了这些设置,我将在接下来的章节中向你展示如何使用这些设置来配置小工具。

表 18-7 。滑块小工具的设置

环境 描述
animate true时,当用户点击手柄外的位置时,动画显示滑块。默认为false
disabled 设置为true时禁用滑块。默认为false
max 定义滑块的最大值。默认为100
min 定义滑块的最小值。默认为0
orientation 定义滑块的方向;详见清单 18-19 。
range values设置一起使用,创建多手柄滑块。
step 定义滑块在minmax值之间移动的间隔。
value 定义滑块表示的值。
values 与 range 设置一起使用,创建多手柄滑块。

image 提示minmax的值是独占的,也就是说如果你设置了0min值和100max值,用户可以在199之间选择值。

更改滑块方向

默认情况下,滑块是水平的,但是你也可以使用orientation设置来创建垂直滑块,如清单 18-19 中的所示。

清单 18-19 。使用方向设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <style>
        #hslider, #vslider { margin: 10px}
    </style>
     <script type="text/javascript">
         $(document).ready(function () {
             $("#hslider").slider({
                 value: 35
             });

             $("#vslider").slider({
                 orientation: "vertical",
                 value: 35
             });
         });
    </script>
</head>
<body>
    <div id="hslider"></div>
    <div id="vslider"></div>
</body>
</html>

我创建了两个滑块,其中一个的orientation设置为vertical。我还改变了style元素,这样我就可以给滑块元素加一个边距,让它们分开。您通过设计底层元素的样式来控制滑块(以及任何 jQuery UI 小部件)的大小和位置(这就是为什么div元素工作得最好;它们可以很容易地用 CSS 来操作)。您可以在图 18-18 中看到滑块。注意,我使用了value设置来设置手柄的初始位置。

9781430263883_Fig18-18.jpg

图 18-18 。创建垂直和水平滑块

尽管我将选项和方法分开,但我可以用不同的方式编写清单 18-19 来更好地利用底层的 jQuery 功能,如清单 18-20 中的所示。

清单 18-20 。更好地利用 jQuery

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <style>
        #hslider, #vslider { margin: 10px}
    </style>
     <script type="text/javascript">
         $(document).ready(function () {
             $("#hslider, #vslider").slider({
                 value: 35,
                 orientation: "vertical"
             }).filter("#hslider").slider("option", "orientation", "horizontal");
         });
    </script>
</head>
<body>
    <div id="hslider"></div>
    <div id="vslider"></div>
</body>
</html>

这是一个小问题,但是我不希望您忘记 jQuery UI 是建立在 jQuery 之上的,并且与 jQuery 紧密集成,您可以使用在本书前面看到的所有选择和操作。

image 提示注意,我将初始方向设置为vertical,然后将其更改为horizontal。滑块有一个错误,在滑块创建后将方向改为vertical会导致手柄错位。

制作滑块动画

animate设置使得当用户在他希望手柄移动到的点点击滑块时,手柄能够平滑移动(与通过拖动滑块来设置值相反)。您可以通过将animate设置为true来启用默认动画,通过使用fastslow来设置动画的速度,或者指定动画应该持续的毫秒数。清单 18-21 显示了animate设置的使用。

清单 18-21 。使用动画设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-1.7.js" type="text/javascript"></script>
    <script src="jquery-ui-1.8.16.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.8.16.custom.css"/>
    <style type="text/css">
        #slider {margin: 10px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#slider").slider({
                animate: "fast"
            });
        });
    </script>
</head>
<body>
    <div id="slider"></div>
</body>
</html>

我已经将animate设置为fast。很难在截图中显示动画,但是图 18-19 显示了animate设置的作用。

9781430263883_Fig18-19.jpg

图 18-19 。制作手柄运动的动画

这个截图显示了我点击鼠标按钮之前的滑块。如果我没有启用动画,那么句柄将会捕捉到我单击的位置,立即为滑块设置新的值。但是由于我已经启用了动画,滑块会以一种不那么不和谐的方式优雅地移动到它的新位置。然而,像任何效果或动画一样,我不想过度渲染效果,这就是为什么我选择了fast选项。

image 提示这是一个例子,您需要使用它来查看完整的结果。如果你不想输入代码和 HTML,你可以在 Apress 网站的源代码/下载区找到本章的代码样本。它可以从apress.com免费获得,包含了本书中的所有例子。

创建范围滑块

范围滑块有两个手柄,让用户指定一个范围的值,而不是单个值。例如,您可能希望让用户表达她愿意为产品支付的价格范围,以便您可以过滤掉其他任何内容。清单 18-22 演示了如何创建一个范围滑块。

清单 18-22 。创建范围滑块

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <style>
        #slider {margin: 20px}
    </style>
     <script type="text/javascript">
         $(document).ready(function () {
             $("#slider").slider({
                 values: [35, 65],
                 range: true,
                 create: displaySliderValues,
                 slide: displaySliderValues
             });

             function displaySliderValues() {
                 $("#lower").text($("#slider").slider("values", 0));
                 $("#upper").text($("#slider").slider("values", 1));
             }
         });
    </script>
</head>
<body>
    <div id="slider"></div>
    <div>Lower Value: <span id="lower">
    </span> Upper Value: <span id="upper"></span></div>
</body>
</html>

要创建一个范围滑块,必须将range设置为true,并将值setting设置为一个数组,该数组包含范围的初始下限和上限。(使用常规滑块时,使用value设置,使用范围滑块时,使用values设置。)在这个例子中,我将界限设置为 35 和 65。你可以在图 18-20 中看到效果。

9781430263883_Fig18-20.jpg

图 18-20 。创建范围滑块

我为createslide事件添加了一个处理函数。我将在“使用滑块事件”一节中讨论滑块支持的事件,但是我想演示如何获得范围滑块中手柄的位置。您可以通过values方法,指定您感兴趣的滑块的索引,如下所示:

...
$("#slider").slider("values", 0);
...

索引是从零开始的,因此该语句获取代表范围下限的句柄值。我使用事件来设置两个span元素的内容。

使用滑块方法

滑块定义了所有 jQuery UI 小部件定义的相同的一组基本方法,还定义了几个允许您设置单个值或要显示的值范围的方法。表 18-8 描述了这些方法。

表 18-8 。滑块方法

方法 描述
slider("destroy") 将基础元素返回到其原始状态
slider("disable") 禁用滑块
slider("enable") 启用滑块
slider("option") 设置一个或多个选项;有关配置 jQuery UI 小部件的详细信息,请参见“配置按钮”一节
slider("value", value) 获取或设置常规滑块的值
slider("values", [values]) 获取或设置范围滑块的值

清单 18-23 展示了如何使用valuevalues方法以编程方式控制滑块。

清单 18-23 。以编程方式控制滑块

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <style>
        #slider, #rangeslider, *.inputDiv { margin: 10px}
        label {width: 80px; display: inline-block; margin: 4px}
    </style>
     <script type="text/javascript">
         $(document).ready(function () {

             $("#slider").slider({
                 value: 50,
                 create: function () {
                     $("#slideVal").val($("#slider").slider("value"));
                 }
             });

             $("#rangeslider").slider({
                 values: [35, 65],
                 range: true,
                 create: function () {
                     $("#rangeMin").val($("#rangeslider").slider("values", 0));
                     $("#rangeMax").val($("#rangeslider").slider("values", 1));
                 }
             })

             $("input").change(function (e) {
                 switch (this.id) {
                     case "rangeMin":
                     case "rangeMax":
                         var index = (this.id == "rangeMax") ? 1 : 0;
                         $("#rangeslider").slider("values", index, $(this).val())
                         break;
                     case "slideVal":
                         $("#slider").slider("value", $(this).val())
                         break;
                 }
             })
         });
    </script>
</head>
<body>
    <div id="rangeslider"></div>
    <div class="inputDiv">
        <label for="rangeMin">Range Min: </label><input id="rangeMin" />
        <label for="rangeMax">Range Max: </label><input id="rangeMax" />
    </div>
    <div id="slider"></div>
    <div class="inputDiv">
        <label for="slideVal">Slide Val: </label><input id="slideVal" />
    </div>
</body>
</html>

该文档包含两个滑块和三个input元素,允许在不移动手柄本身的情况下指定手柄的值。你可以在图 18-21 的中看到元件的布局。

9781430263883_Fig18-21.jpg

图 18-21 。以编程方式控制滑块

我使用 jQuery 来选择input元素,并调用change方法来为change事件设置一个处理程序,这意味着每当input元素之一的值发生变化时,就会调用该函数。

在处理函数中,我使用触发事件的元素的id属性来判断我需要操作哪个滑块,并调用valuevalues方法来设置句柄位置。input元素和滑块之间的关系是单向的,这意味着移动手柄不会更新input元素。在下一节中,我将向您展示如何创建双向关系。

使用滑块事件

表 18-9 显示了滑块支持的事件。这些事件最好的特性是同时支持changestop,这允许您区分用户移动句柄所创建的新值和您以编程方式设置的值。

表 18-9 。滑块事件

事件 描述
create 创建滑块时触发
start 当用户开始滑动手柄时触发
slide 手柄滑动时,每次鼠标移动都会触发
change 当用户停止滑动手柄或以编程方式更改值时触发
stop 当用户停止滑动手柄时触发

清单 18-24 展示了使用滑块事件在滑块和input元素之间创建双向关系,类似于上一节中的例子,尽管为了保持例子简单,我已经删除了其中一个滑块。这种双向关系允许我将编程支持和用户交互结合起来管理滑块。

清单 18-24 。使用滑块事件在滑块和输入之间创建双向关系

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js"></script>
    <script src="jquery-ui-1.10.3.custom.js"></script>
    <link href="jquery-ui-1.10.3.custom.css" rel="stylesheet" />
    <style>
        #rangeslider, *.inputDiv { margin: 10px}
        label {width: 80px; display: inline-block; margin: 4px}
    </style>
     <script type="text/javascript">
         $(document).ready(function () {
             $("#rangeslider").slider({
                 values: [35, 65],
                 range: true,
                 create: setInputsFromSlider,
                 slide: setInputsFromSlider,
                 stop: setInputsFromSlider
             });

             function setInputsFromSlider() {
                 $("#rangeMin").val($("#rangeslider").slider("values", 0));
                 $("#rangeMax").val($("#rangeslider").slider("values", 1));
             }

             $("input").change(function (e) {
                 var index = (this.id == "rangeMax") ? 1 : 0;
                 $("#rangeslider").slider("values", index, $(this).val())
             });
         });
    </script>
</head>
<body>
    <div id="rangeslider"></div>
    <div class="inputDiv">
        <label for="rangeMin">Range Min: </label><input id="rangeMin" />
        <label for="rangeMax">Range Max: </label><input id="rangeMax" />
    </div>
</body>
</html>

在这个例子中,我处理了createslidestop事件。现在,当向输入元素中输入新值时,滑块手柄会移动,当滑块移动时,输入元素中的值也会更新。你可以在图 18-22 中看到文档的样子,但这是一个需要交互才能看到完整效果的例子。

9781430263883_Fig18-22.jpg

图 18-22 。响应滑块事件

摘要

在本章中,我向您介绍了前三个 jQuery UI 小部件:按钮、进度条和滑块。每个小部件都遵循相同的基本结构:有一个方法可以创建和配置小部件,并让您提供响应其事件的函数。有些方法和事件是每个小部件共有的,但也有一些独特的附加功能,它们揭示了一些小部件提供的特殊功能。现在我已经有了基本的方法,我将在接下来的章节中向您展示一些更加灵活和复杂的小部件。在第十九章中,我向你展示了自动完成和折叠部件。

十九、使用自动完成和折叠部件

在本章中,我将描述 jQuery UI 自动完成和 accordion 小部件。它们比我在第十八章中展示的小部件更复杂,但是它们遵循相同的设置、方法和事件模式。这些是高度可配置的、灵活的、聪明的用户界面控件,如果使用得当,它们可以显著增强文档和 web 应用的外观和可用性。表 19-1 对本章进行了总结。

表 19-1 。章节总结

问题 解决办法 列表
input元素添加 jQuery UI 自动完成特性 使用autocomplete方法 1, 2
从远程服务器获取自动完成建议 source设置为 URL 3–5
动态生成自动完成建议 source设置指定一个功能 six
从异步任务返回自动完成结果 调用response函数 seven
控制自动完成弹出窗口的位置 使用position属性 eight
以编程方式控制自动完成功能 使用searchclose方法 nine
接收所选自动完成项目的通知 使用focusselectchange事件 Ten
修改向用户显示的自动完成结果 处理response事件 Eleven
覆盖默认的自动完成操作 覆盖select事件的默认动作 Twelve
创建 jQuery UI 折叠小部件 使用accordion方法 Thirteen
设置折叠面板及其内容面板的高度 使用heightStyle设置 14–16
更改用户激活内容元素时必须执行的操作 使用event设置 Seventeen
在 jQuery UI accordion 中设置活动内容元素 使用activecollapsible设置 18, 19
更改手风琴使用的图标 使用icons设置 Twenty
当折叠面板中的活动元素更改时接收通知 处理activatebeforeactivate事件 Twenty-one

自上一版以来,JQUERY UI 发生了变化

在 jQuery UI 1.10 版本中,accordion 小部件的 API 发生了很多变化:配置选项、方法和事件都发生了变化。我在本章的相关章节中列出了这些变化。

我在本章中也描述了 autocomplete 小部件,它有一个有用的新特性:您可以控制为用户提供完成选项的弹出窗口的位置(参见“定位弹出窗口”一节)。

使用 jQuery UI 自动完成

当用户在input元素中输入值时,autocomplete 小部件向用户提供建议。如果使用得当,这个小部件可以为用户节省时间,加快数据输入并减少错误。在接下来的小节中,我将向您展示如何创建、配置和使用 jQuery UI autocomplete 小部件。

创建自动完成元素

您可以在一个input元素上使用autocomplete方法来创建一个自动完成小部件。清单 19-1 演示了如何设置基本的自动完成功能。

清单 19-1 。创建自动完成输入元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            var flowers = ["Aster", "Daffodil", "Rose", "Peony", "Primula", "Snowdrop",
                           "Poppy", "Primrose", "Petuna", "Pansy"];

            $("#acInput").autocomplete({
                source: flowers
            })
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
</body>
</html>

就像你见过的其他 jQuery UI 方法一样,autocomplete方法被应用于 HTML 元素,并且配置了一个定义了source属性的 map 对象。此属性指定自动完成条目的来源。

您可以为自动完成值使用一系列不同的数据源,我将在本章的后面演示。在清单 19-1 中,我使用了一个简单的值数组。图 19-1 展示了自动完成功能呈现给用户的方式。

9781430263883_Fig19-01.jpg

图 19-1 。一个基本的 jQuery UI 自动完成元素

image 注意自动完成特性不强制任何类型的验证,用户可以在input元素中输入任何值,而不仅仅是那些由source设置定义的值。

图 19-1 中有两张截图。第一个显示了当我输入字母 P 时会发生什么。如您所见,第一个屏幕截图显示了包含字母 P 的数据项列表。这个列表包括以 P 开头的花名,但也包括雪花莲(因为它包含字母 p )。在第二个截图中,我输入了 Pe ,jQuery UI 只显示包含该字母组合的条目。用户可以继续键入条目或从自动完成列表中选择一个条目。

image 提示在文档中,我把input元素和它的label放在一个属于ui-widget类的div元素里面。这将设置labelinput元素的 CSS 字体属性,以匹配自动完成弹出窗口使用的字体属性。我会在第三十五章中解释更多关于如何使用 jQuery UI CSS 类的内容。

使用对象数组作为数据源

另一种方法是使用对象数组,而不是字符串。这允许我将弹出菜单中显示的标签与插入到input元素中的值分开,如清单 19-2 所示。

清单 19-2 。使用对象数组进行自动完成

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            var flowers = [{label: "Aster (Purple)", value: "Aster"},
                {label: "Daffodil (White)", value: "Daffodil"},
                {label: "Rose (Pink)", value: "Rose"},
                {label: "Peony (Pink)", value: "Peony"}]

            $("#acInput").autocomplete({
                source: flowers
            })

        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
</body>
</html>

当使用对象数组时,autocomplete 特性会寻找名为labelvalue的属性。label属性用于创建弹出列表,如果选择了项目,则value条目将被插入到input元素中。在清单 19-2 中,我给标签添加了一些颜色信息,这些信息在标签被选中时不包含在值中,如图图 19-2 所示。

9781430263883_Fig19-02.jpg

图 19-2 。使用对象数组将标签与值分开

配置自动完成

自动完成功能支持许多设置,让您控制其功能的不同方面,如表 19-2 所述。在接下来的小节中,我将向您展示如何使用这些设置来配置小部件。

表 19-2 。自动完成设置

环境 描述
appendTo 指定弹出菜单应追加到的元素。默认为body元素。
autoFocus 如果设置为true,列表中的第一个项目将获得焦点,这意味着用户可以通过按回车键来选择该项目。默认为false
delay 指定击键后的延迟时间(以毫秒为单位),在此之后自动完成数据被更新。默认为300
disabled 设置为true时,禁用自动完成功能。这个设置不影响底层的input元素。默认是false
minLength 指定在显示自动完成菜单之前,用户必须键入的最少字符数。默认为1
position 设置弹出菜单相对于输入元素的位置。
source 指定要添加到自动完成菜单中的项目的源。该设置没有默认值,必须在调用autocomplete方法时指定。

使用远程数据源

最有趣的自动完成设置是source,因为您可以使用它来处理各种不同类型的数据,以填充弹出菜单。我在清单 19-2 中使用了一个 JavaScript 数组,这对于简单的静态数据列表来说很好。对于更复杂的情况,您可以从服务器获取匹配项的列表。你所要做的就是指定将生成数据的 URL,如清单 19-3 所示。

清单 19-3 。使用远程数据源

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#acInput").autocomplete({
                source: "[`node.jacquisflowershop.com/auto`](http://node.jacquisflowershop.com/auto)"
            })
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
</body>
</html>

当 jQuery UI 需要弹出自动完成菜单的项目列表时,它将向指定的 URL 发出 HTTP GET 请求。使用键term将用户到目前为止键入的字符包含在请求查询字符串中。例如,如果用户输入了字母 s ,那么 jQuery UI 将请求下面的 URL:

http://node.jacquisflowershop.com/auto?term=s

如果用户随后键入字母 n ,jQuery UI 将请求以下内容:

http://node.jacquisflowershop.com/auto?term=sn

当有很多数据项并且您不想将它们都发送给客户端时,这种技术非常有用。当项目列表动态变化,并且您希望确保用户受益于最新的可用数据时,它也很有用。

服务器负责从查询字符串中获取term值,并返回一个 JSON 字符串,表示要显示给用户的项目数组。清单 19-4 展示了我是如何为Node.js脚本更新formserver.js脚本来做到这一点的。(有关获取和安装Node.js的详细信息,请参见第一章。)

清单 19-4 。支持远程自动完成的 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);

    var flowers = ["Aster", "Daffodil", "Rose", "Peony", "Primula", "Snowdrop",
                    "Poppy", "Primrose", "Petuna", "Pansy"];

    var matches = [];
    var term = url.parse(req.url, true).query["term"];

    if (term) {
        var pattern = new RegExp("^" + term, "i");
        for (var i = 0; i < flowers.length; i++) {
            if (pattern.test(flowers[i])) {
                matches.push(flowers[i]);
            }
        }
    } else {
        matches = flowers;
    }

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

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

这个Node.js脚本使用与清单 19-2 中的相同的一组花名,并返回那些与浏览器发送的词语相匹配的花名。我稍微改变了搜索,只返回那些以单词开头的名字。例如,如果 jQuery UI 发送如下请求:

http://node.jacquisflowershop.com/auto?term=p

然后Node.js服务器将返回下面的 JSON:

["Peony","Primula","Poppy","Primrose","Petuna","Pansy"]

因为我是在花名开头匹配,所以列表中省略了雪花莲,如图图 19-3 所示。

9781430263883_Fig19-03.jpg

图 19-3 。从远程服务器获取自动完成条目

这是一种很好的技术,但是它会对服务器产生很多请求。在我的例子中这不是问题,因为我正在执行一个简单的搜索,我的服务器和浏览器在同一个网络上。但是对于复杂的搜索,在可能遭受延迟的广域网上,服务器上的负载可能成为一个问题。

管理自动完成请求速率的最佳方式是使用minLengthdelay设置。minLength设置指定了在 jQuery UI 向服务器发出自动完成请求之前,用户必须输入的字符数。您可以使用此设置,以便仅在输入几个字符后向服务器请求数据,此时您已经有足够的信息来缩小搜索范围。

delay设置指定了按键后自动完成信息被请求的时间。您可以使用此设置来防止在用户快速键入时发出请求。因此,如果用户键入 sn ,您可以避免点击服务器获取s列表,然后立即再次获取sn列表。通过组合这些设置,您可以减少请求的数量,并在需要时为用户提供指导。清单 19-5 显示了这些设置的用法。

清单 19-5 。使用 delay 和 minLength 设置减少服务器请求

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#acInput").autocomplete({
                source: "http://node.jacquisflowershop.com/auto",
                minLength: 3,
                delay: 1000
            })
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
</body>
</html>

在清单 19-5 中,直到用户输入了三个字符并且在一秒钟内没有输入任何额外的字符,才会向服务器发出初始请求。

使用函数作为数据源

您可以使用函数为自动完成条目创建真正定制的源。您将该函数分配给source设置,每次 autocomplete 特性需要向用户显示项目时都会调用它。清单 19-6 展示了。

清单 19-6 。使用函数生成自动完成项目

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            var flowers = ["Aster", "Daffodil", "Rose", "Peony", "Primula", "Snowdrop",
                            "Poppy", "Primrose", "Petuna", "Pansy"];

            $("#acInput").autocomplete({
                source: function(request, response) {
                    var term = request.term;
                    var pattern = new RegExp("^" + term, "i");

                    var results = $.map(flowers, function(elem) {
                        if (pattern.test(elem)) {
                            return elem;
                        }
                    })
                    response(results);
                }
            })
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
</body>
</html>

两个参数被传递给该函数。第一个参数是一个只有一个名为term的属性的对象。该属性的值是用户输入到input元素中的字符串。第二个参数是一个函数,当您生成了想要显示给用户的自动完成项目列表时,您将调用这个函数。这个函数的参数是一个字符串或对象的数组。

在清单 19-6 中,我复制了来自清单 19-5 的服务器端功能,并且我生成了一个数组,包含那些以指定术语开始的条目。

image 提示我使用 jQuery map工具方法处理了数组的内容,我在第三十四章中对此进行了描述。

然后,我通过将数组作为参数传递给response函数,将结果传递回 jQuery UI,如下所示:

...
response(results);
...

这似乎是一种奇怪的处理结果的方式,但这意味着您可以在异步任务完成后调用该函数。在清单 19-7 中,您可以看到我是如何使用一个函数发出 Ajax 请求来获取花的细节,对返回的内容进行本地搜索,然后调用response函数将最终结果提供给 jQuery UI。

清单 19-7 。使用执行异步任务的自定义数据源函数

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        button {margin-bottom: 5px}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#acInput").autocomplete({
                source: function (request, response) {
                    $.getJSON("[`node.jacquisflowershop.com/auto`](http://node.jacquisflowershop.com/auto)",
                        function(flowers) {
                            var term = request.term;
                            var pattern = new RegExp("^" + term, "i");

                            var results = $.map(flowers, function (elem) {
                                if (pattern.test(elem)) {
                                    return elem;
                                }
                            })
                            response(results);
                        });
                }
            })
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>

    </form>
</body>
</html>

在这个例子中,我使用getJSON方法从Node.js服务器获取完整的 flower 值集合。我在本地搜索匹配项,当我有一组建议要呈现给 jQuery UI 时,调用response函数。

定位弹出窗口

默认情况下,允许用户选择一个值的弹出窗口将出现在input元素下面,但是您可以通过设置position属性来改变这一点——尽管这样做的语法有点笨拙。清单 19-8 显示了一个改变位置的例子。

清单 19-8 。更改弹出窗口的位置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style>
        #target { margin-top: 40px; display:inline-block }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {

            var flowers = [{ label: "Aster (Purple)", value: "Aster" },
                { label: "Daffodil (White)", value: "Daffodil" },
                { label: "Rose (Pink)", value: "Rose" },
                { label: "Peony (Pink)", value: "Peony" }]

            $("#acInput").autocomplete({
                source: flowers,
                position: {
                    my: "left top",
                    at: "right bottom+20",
                    of: "#target",
                    collision: "fit"
                }
            })

        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
    <span id="target">Target</span>
</body>
</html>

position属性是用一个对象配置的,该对象为放置弹出窗口的策略的不同方面指定了属性,我已经在表 19-3 的列表中描述了我使用的四个属性。

表 19-3 。自动完成位置属性

名字 描述
my 指定将用于确定位置的弹出窗口部分(有关可用值范围的详细信息,请参见下文)
at 指定弹出窗口相对于目标元素的位置(有关可用值范围的详细信息,请参见下文)
of 指定弹出窗口将相对于其定位的目标元素;如果省略,这就是input元素,但是可以指定为HTMLElement、选择器或jQuery对象
collision 指定当弹出窗口溢出窗口时应如何调整弹出窗口的位置(表 19-4 显示了该属性的值)

表 19-4 。自动完成位置属性

名字 描述
flip jQuery UI 检查更多弹出窗口是否可以显示在由of属性指定的元素的另一侧;将选择显示最多弹出窗口的一侧
fit jQuery UI 将弹出窗口从窗口边缘移开
flipfit 结合了flipfit值的行为
none 告诉 jQuery UI 不要调整弹出窗口的位置

image 提示自动完成弹出窗口是使用 jQuery UI position 工具特性放置的,它比我在本章中使用的配置选项更多。更多详情见http://api.jqueryui.com/position

使用指定水平和垂直位置的值设置myat属性,用空格分隔。水平值为leftrightcenter,垂直值为topbottomcenter。您还可以指定相对于某个位置的偏移量,可以是百分比,也可以是像素数。在清单中,您可以看到我已经如下设置了myatof属性:

...
my: "left top",
at: "right bottom+20",
of: "#target",
...

这种组合意味着:将 autocomplete 弹出窗口的左上角放置在 id 为target的元素的右下角下方 20 个像素的位置。你通常不会使用of属性来指定一个元素,因为它破坏了弹出菜单和input元素之间的视觉关联,但是我想证明在弹出菜单的放置上有很大的灵活性,你可以在图 19-4 中看到这些配置属性的效果。

9781430263883_Fig19-04.jpg

图 19-4 。配置自动完成弹出窗口的位置

collision属性指定如果弹出窗口不适合可用空间会发生什么。表 19-4 描述了该属性支持的值。

我为collision属性选择了fit值,这意味着 jQuery UI 将移动弹出窗口以适应浏览器窗口,如图图 19-5 所示。

9781430263883_Fig19-05.jpg

图 19-5 。移动自动完成弹出窗口以适应浏览器窗口的边缘

使用自动完成方法

jQuery UI 自动完成特性支持许多方法,您可以使用这些方法来操作自动完成过程。表 19-5 描述了这些方法。

表 19-5 。自动完成方法

方法 描述
autocomplete("close") 关闭自动完成菜单
autocomplete("destroy") 从 input 元素中删除自动完成功能
autocomplete("disable") 禁用自动完成
autocomplete("enable") 启用自动完成
autocomplete("option") 设置一个或多个选项
autocomplete("search", value) 使用指定值显式触发 autocomplete 如果没有提供值参数,则使用input元素的内容

autocomplete 小部件特有的两种方法是searchclose,您可以使用它们显式地开始和结束自动完成过程,如清单 19-9 所示。

清单 19-9 。使用搜索和关闭方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        button {margin-bottom: 5px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            var flowers = ["Aster", "Daffodil", "Rose", "Peony", "Primula", "Snowdrop",
                            "Poppy", "Primrose", "Petuna", "Pansy"];

            $("#acInput").autocomplete({
                source: flowers
            });

            $("button").click(function(e) {
                e.preventDefault();
                switch (this.id) {
                    case "close":
                        $("#acInput").autocomplete("close");
                        break;
                    case "input":
                        $("#acInput").autocomplete("search");
                        break;
                    default:
                        $("#acInput").autocomplete("search", this.id);
                        break;
                }
            });
        });
    </script>
</head>
<body>
    <form>
        <button id="s">S</button>
        <button id="p">P</button>
        <button id="input">Input Content</button>
        <button id="close">Close</button>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
</body>
</html>

我添加了button元素,并使用 jQuery click方法来设置不同的自动完成方法调用。当标有 SP 的按钮被按下时,我调用search方法,将选择的字母作为搜索值传入。这将触发使用所选字母的自动完成功能,而不考虑输入元素的内容,如图图 19-6 所示。

9781430263883_Fig19-06.jpg

图 19-6 。使用带有搜索词的搜索方法

如图所示,弹出菜单显示包含按钮字母的条目,即使input元素包含单词hello

Input Content按钮使用input元素中包含的任何字符触发自动完成功能,如图图 19-7 所示。

9781430263883_Fig19-07.jpg

图 19-7 。使用输入元素的内容进行搜索

最后一个按钮Close调用close方法来关闭弹出菜单。

使用自动完成事件

自动完成功能定义了许多事件,如表 19-6 所述。

表 19-6 。自动完成事件

事件 描述
change 当值改变后焦点离开input元素时触发
close 当弹出菜单关闭时触发
create 创建自动完成功能时触发
focus 当弹出菜单中的项目获得焦点时触发
open 显示弹出菜单时触发
response 在搜索完成后、向用户显示结果前触发
search 在生成或请求自动完成项目列表之前触发
select 从菜单中选择项目时触发

获取所选项目的详细信息

jQuery UI 通过第二个参数(通常称为ui)提供关于事件的附加信息。对于changefocusselect事件,jQuery UI 为ui对象提供了一个item属性,该属性返回一个描述从弹出菜单中选择或聚焦的项目的对象。清单 19-10 展示了如何使用这个特性来获取物品的信息。

清单 19-10 。在事件处理程序中使用 ui 对象

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            var flowers = ["Aster", "Daffodil", "Rose", "Peony", "Primula", "Snowdrop",
                            "Poppy", "Primrose", "Petuna", "Pansy"];

            $("#acInput").autocomplete({
                source: flowers,
                focus: displayItem,
                select: displayItem,
                change: displayItem
            })

            function displayItem(event, ui) {
                $("#itemLabel").text(ui.item.label)
            }
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
            Item Label: <span id="itemLabel"></span>
        </div>
    </form>
</body>
</html>

我添加了一个span元素,用来显示所选对象的label属性。jQuery UI 创建具有labelvalue属性的对象,即使您为source设置使用简单的字符串数组,因此您总是需要从ui.item对象中读取这些属性中的一个。在这个例子中,我使用相同的函数来显示来自focusselectchange事件的项目。你可以在图 19-8 中看到效果。

9781430263883_Fig19-08.jpg

图 19-8 。获取所选项目的详细信息

修改搜索结果

在结果显示给用户之前,response事件提供了修改结果的机会。在清单 19-11 的中,您可以看到我是如何处理response事件来防止Peony值被显示出来的。

清单 19-11 。处理响应事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function () {

            var flowers = ["Aster", "Daffodil", "Rose", "Peony", "Primula", "Snowdrop",
                            "Poppy", "Primrose", "Petuna", "Pansy"];

            $("#acInput").autocomplete({
                source: flowers,
                response: filterResults
            });

            function filterResults(event, ui) {
                for (var i = 0; i < ui.content.length; i++) {
                    if (ui.content[i].label == "Peony") {
                        ui.content.splice(i, 1);
                    }
                }
            }
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
        </div>
    </form>
</body>
</html>

我定义了一个名为filterResults的函数来处理response事件。在这个函数中,我列举了将呈现给用户的结果,这些结果作为一个数组通过ui.content属性访问。response事件的处理函数必须直接修改数组,所以我使用splice方法从ui.content数组中移除Peony条目。

覆盖默认选择动作

select事件有一个默认动作,就是用从弹出菜单中选择的项目的value属性的内容替换input元素的内容。这正是大多数情况下所需要的,但是这个事件可以用来补充默认操作或阻止它,并做一些完全不同的事情。清单 19-12 包含了一个通过设置相关字段的值来补充默认值的例子。

清单 19-12 。覆盖选择事件的默认操作

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            var flowers = ["Aster", "Daffodil", "Rose"];

            var skus = { Aster: 100, Daffodil: 101, Rose: 102};

            $("#acInput").autocomplete({
                source: flowers,
                select: function(event, ui) {
                    $("#sku").val(skus[ui.item.value]);
                }
            })
        });
    </script>
</head>
<body>
    <form>
        <div class="ui-widget">
            <label for="acInput">Flower Name: </label><input id="acInput"/>
            <label for="sku">Stock Keeping Unit: </label><input id="sku"/>
        </div>
    </form>
</body>
</html>

image 提示我在第九章的中描述了事件的默认动作。

当触发select事件时,我的处理函数使用ui参数获取所选项的值,并设置相关字段的值——在本例中是库存单位,它是从skus对象中获得的。这样,我可以根据初始选择为其他字段提供默认值,从而帮助用户。这在很多情况下都很有用,尤其是在选择送货地址等项目时。你可以在图 19-9 中看到结果,尽管这是一个你应该加载浏览器以获得完整效果的例子。本文档和本书中所有其他示例的 HTML 可以在 Apress 网站的源代码/下载区域免费获得(www.apress.com)。

9781430263883_Fig19-09.jpg

图 19-9 。使用 select 事件填充另一个字段

使用 jQuery UI 手风琴

accordion 小部件接受一组内容元素并将其呈现出来,这样用户最多只能看到一个内容元素。当用户选择另一个时,可见内容被隐藏,产生一种让人想起同名乐器中风箱的效果。

当你不想一次显示所有内容而让用户不知所措时,手风琴非常适合呈现可以分成离散部分的内容。理想情况下,各个内容部分共享一些可以使用简单标题表达的总体主题。

制作手风琴

jQuery UI accordion 小部件是使用accordion方法应用的,如清单 19-13 所示。

清单 19-13 。制作手风琴

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        #accordion {margin: 5px}
        .dcell img {height: 60px}
    </style>
    <script id="flowerTmpl" type="text/x-jquery-tmpl">
        {{#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 () {
            var data = {
                flowers: [{ "name": "Aster", "product": "aster" },
                { "name": "Daffodil", "product": "daffodil" },
                { "name": "Rose", "product": "rose" },
                { "name": "Peony", "product": "peony" },
                { "name": "Primula", "product": "primula" },
                { "name": "Snowdrop", "product": "snowdrop" },
                { "name": "Carnation", "product": "carnation" },
                { "name": "Lily", "product": "lily" },
                { "name": "Orchid", "product": "orchid" }]
            };

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

            $("#accordion").accordion();

            $("button").button();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="accordion">
            <h2><a href="#">Row 1</a></h2>
            <div id="row1"></div>
            <h2><a href="#">Row 2</a></h2>
            <div id="row2"></div>
            <h2><a href="#">Row 3</a></h2>
            <div id="row3"></div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

这个例子中最重要的部分是元素div的内容,它的idaccordion

...
<div id="accordion">
    <h2><a href="#">Row 1</a></h2>
    <div id="row1"></div>

    <h2><a href="#">Row 2</a></h2>
    <div id="row2"></div>

    <h2><a href="#">Row 3</a></h2>
    <div id="row3"></div>
</div>
...

我已经改变了格式,使结构更加明显。顶层的div元素是用accordion方法定位的元素。jQuery UI 在div的内容中寻找头元素(从h1h6元素),并分解内容,使得每个头都与它后面的元素相关联。在本例中,我使用了h2元素作为标题,每个标题后面都有一个div元素。我使用数据模板用花店提供的产品细节填充这些div元素。

注意,我在每个h2元素中添加了一个a元素。这是指定每个内容部分标题的方法。你可以在图 19-10 中看到 jQuery UI 是如何转换顶层div元素及其内容的。

9781430263883_Fig19-10.jpg

图 19-10 。jQuery UI 折叠

image 提示href属性设置为#是定义仅用于 JavaScript 的a元素时的常用技术。我使用这种方法是因为它使示例更简单,但是我通常推荐使用 jQuery 动态插入a元素,这样它们就不会干扰非 JavaScript 用户。

当创建 accordion 时,第一个内容部分被显示,而其他部分被隐藏。a元素的内容被用作每个部分的标签,点击一个标签关闭当前部分并打开所选部分(在过渡期间有一个很好的动画效果,我无法使用截图显示)。你可以在图 19-11 中看到点击标题的效果。

9781430263883_Fig19-11.jpg

图 19-11 。手风琴过渡

配置折叠面板

accordion 支持许多配置 设置,可用于微调其行为。表 19-7 描述了这些设置,我将在接下来的章节中向你展示如何使用这些设置来配置小工具。

表 19-7 。手风琴设置

环境 描述
active 获取或设置要显示的内容元素。默认情况下,最初显示第一个内容元素。
animate 指定从一个内容元素过渡到另一个内容元素时将使用的动画。有关 jQuery UI 动画的详细信息,请参见第三十五章。
collapsible true时,可以折叠所有的内容部分。默认为false
disabled true时,手风琴被禁用。默认为false
event 指定 header 元素中触发转换到另一个 content 元素的事件。默认为click
header 指定哪些元素将用作标题。
heightStyle 控制手风琴及其面板的高度。
icons 指定折叠面板中使用的图标。

image 提示在 jQuery UI 1.10 中,animate设置取代了animated设置;heightStyle选项替代autoHeightclearStylefillSpace设置;并且当使用icons属性时,使用新的属性名称来指定用于所选内容面板(activeHeader)的图标。

设置手风琴的高度

属性控制手风琴及其面板的高度。有三个支持的值,我在表 19-8 中描述过。

表 19-8 。heightStyle 的值

名字 描述
auto 所有面板将被设置为最高面板的高度
fill 扩展折叠面板以填充父元素的高度
content 每个面板都与其内容一样高

您可以根据内容元素的高度或父元素的高度来设置折叠面板的高度。最常见的技术是依赖于默认的auto值,它将所有的内容元素设置为相同的高度(最高的内容元素的高度),并根据该高度调整折叠的大小。

这是我在前面的例子中使用的方法,尽管在使用包含图像的内容元素时需要小心,特别是当使用 jQuery 将img元素插入到文档中时。问题是,在加载所有图像之前,可以调用accordion方法,这导致 jQuery UI 从浏览器获得关于内容元素高度的误导信息。在我的示例文档中,内容div元素的高度在图像加载前是 55 像素,加载后是 79 像素。当 accordion 显示出意外的滚动条来显示内容时,你就可以判断你是否碰到了这个问题,如图图 19-12 所示。

9781430263883_Fig19-12.jpg

图 19-12 。不正确的高度信息导致的问题

当加载图像时,jQuery UI 不会检测到内容元素高度的变化,最终会错误地显示内容。为了解决这个问题,您需要提供关于内容元素在加载所有外部资源后的高度的信息。有很多方法可以做到这一点,在本例中,我选择为style元素中的img元素设置 CSS height属性,如下所示:

...
<style type="text/css">
    #accordion {margin: 5px}
    .dcell img {height: 60px}
</style>
...

撇开图像问题不谈,当您希望每个内容元素的高度一致时,auto值是有用的,但是当内容元素的大小之间存在很大差异时,它会导致一些不吸引人的视觉效果。清单 19-14 显示了一个script元素,它以不同的方式插入产品信息元素。

清单 19-14 。高度差很大的手风琴

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

        var data = {
            flowers: [{ "name": "Aster", "product": "aster" },
            { "name": "Daffodil", "product": "daffodil" },
            { "name": "Rose", "product": "rose" },
            { "name": "Peony", "product": "peony" },
            { "name": "Primula", "product": "primula" },
            { "name": "Snowdrop", "product": "snowdrop" },
            { "name": "Carnation", "product": "carnation" },
            { "name": "Lily", "product": "lily" },
            { "name": "Orchid", "product": "orchid" }]
        };

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

        $("<h2><a href=#>All</a></h2><div id=row0></div>").prependTo("#accordion")
            .filter("div").append($("#row1, #row2, #row3").clone());

        $("#accordion").accordion();

        $("button").button();
    });
</script>
...

为了创建一个超高的内容元素,我使用 jQuery 克隆了现有的 content div元素,并将它们插入到一个新的内容元素中,创建了一个显示所有产品的面板。这个新面板的高度是其他面板的三倍,这导致当显示较小的内容元素时,accordion 会显示大量的空白空间,如图 19-13 所示。

9781430263883_Fig19-13.jpg

图 19-13 。高度差较大时自动设置的效果

如果一大片空白空间不适合您的应用,那么您可以简单地将heightStyle属性更改为content,如清单 19-15 所示。

清单 19-15 。更改高度样式设置

...
$("#accordion").accordion({
    heightStyle: "content"
});
...

作为内容元素之间过渡的一部分,手风琴现在将改变其高度,如图图 19-14 所示。

9781430263883_Fig19-14.jpg

图 19-14 。手风琴自己调整大小,以适应不同的内容高度

这是一种更简洁的显示内容的方法,但是这意味着页面的布局会随着 accordion 自身的调整而改变。如果控制键不断在屏幕上移动,这可能会让用户感到厌烦。

使用父对象确定手风琴的高度

一种完全不同的方法是设置 accordion 的大小,以便它简单地填充其父元素。当我处理动态生成的内容时,我发现这非常有用,因为我不能很好地控制大小,也不想调整布局。你可以通过fill设置来调整手风琴的大小,如清单 19-16 中的所示。

清单 19-16 。调整折叠面板的大小以填充父元素

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

        var data = {
            flowers: [{ "name": "Aster", "product": "aster" },
            { "name": "Daffodil", "product": "daffodil" },
            { "name": "Rose", "product": "rose" },
            { "name": "Peony", "product": "peony" },
            { "name": "Primula", "product": "primula" },
            { "name": "Snowdrop", "product": "snowdrop" },
            { "name": "Carnation", "product": "carnation" },
            { "name": "Lily", "product": "lily" },
            { "name": "Orchid", "product": "orchid" }]
        };

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

        $("<h2><a href=#>All</a></h2><div id=row0></div>").prependTo("#accordion")
            .filter("div").append($("#row1, #row2, #row3").clone());

        $("#accordion").wrap("<div style='height:300px'></div>");

        $("#accordion").accordion({
            heightStyle: "fill"
        });

        $("button").button();
    });
</script>
...

在这个例子中,我将accordion元素包装在一个新的父div元素中,该元素具有 300 像素的固定大小。当我调用手风琴方法时,我将heightStyle设置为fill。如果父元素比内容元素小,那么 accordion 会添加一个滚动条。如果父元素大于内容元素,则添加填充。在图 19-15 中可以看到滚动条的应用。这是因为显示所有花朵的内容元素比父元素的 300 像素高。

9781430263883_Fig19-15.jpg

图 19-15 。使用手风琴填充父对象的高度

更改事件类型

默认情况下,用户通过单击来打开和关闭内容元素。您可以通过event设置来改变这种行为,如清单 19-17 中的所示。

清单 19-17 。使用事件设置

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

        var data = {
            flowers: [{ "name": "Aster", "product": "aster" },
            { "name": "Daffodil", "product": "daffodil" },
            { "name": "Rose", "product": "rose" },
            { "name": "Peony", "product": "peony" },
            { "name": "Primula", "product": "primula" },
            { "name": "Snowdrop", "product": "snowdrop" },
            { "name": "Carnation", "product": "carnation" },
            { "name": "Lily", "product": "lily" },
            { "name": "Orchid", "product": "orchid" }]
        };

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

        $("#accordion").accordion({
            event: "mouseover"
        });

        $("button").button();
    });
</script>
...

在清单 19-17 中,我使用了event设置来指定内容元素应该被打开以响应mouseover事件(我在第九章中描述过)。这一变化的效果是,一旦鼠标指针进入内容元素的标签,jQuery UI 就会打开该元素并显示其内容。我不能在截图中显示这种效果,但是我建议您加载这个示例,看看它是如何工作的。这是一个简洁的特性,但是我建议您小心使用它。用户通常很快就能理解点击图标打开一部分内容的想法,但是对鼠标事件的响应会带来令人不安和惊讶的用户体验。

选择活动标题

accordion 的默认行为最初是向用户显示第一个内容元素。您可以使用active属性来改变这种行为。您将active设置为想要显示的内容元素的索引,如清单 19-18 所示。

清单 19-18 。使用活动属性

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

        var data = {
            flowers: [{ "name": "Aster", "product": "aster" },
            { "name": "Daffodil", "product": "daffodil" },
            { "name": "Rose", "product": "rose" },
            { "name": "Peony", "product": "peony" },
            { "name": "Primula", "product": "primula" },
            { "name": "Snowdrop", "product": "snowdrop" },
            { "name": "Carnation", "product": "carnation" },
            { "name": "Lily", "product": "lily" },
            { "name": "Orchid", "product": "orchid" }]
        };

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

        $("#accordion").accordion({
            active: 1
        });

        $("button").button();
    });
</script>
...

效果是 accordion 最初在索引 1 处打开行(索引是从零开始的,所以这是第二个内容元素),如图 19-16 所示。

9781430263883_Fig19-16.jpg

图 19-16 。选择要显示的初始内容元素

通过将active设置为false,您也可以不激活任何内容。如果这样做,您还必须将可折叠设置设为true。这将禁用一个内容元素必须始终可见的默认策略。清单 19-19 显示了这些设置的应用。

清单 19-19 。禁用最初活动的内容元素

...
$("#accordion").accordion({
    active: false,
    collapsible: true
});
...

你可以在图 19-17 中看到这些设置的效果。

9781430263883_Fig19-17.jpg

图 19-17 。没有初始活动内容元素的手风琴

accordion 的工作方式和以前一样,只是没有最初活动的内容元素,并且所有的内容元素都可以关闭。当屏幕空间有限并且用户对 accordion 中的内容不感兴趣时,这是一种有用的技术。

更改折叠图标

您可以使用icons设置来更改折叠内容标题中使用的图标。清单 19-20 提供了一个例子。

清单 19-20 。更改手风琴使用的图标

...
$("#accordion").accordion({
    collapsible: true,
    icons: {
        header: "ui-icon-zoomin",
        activeHeader: "ui-icon-zoomout"
    }
});
...

image 提示在 jQuery UI 1.10 中,activeHeader属性取代了headerSelected属性。

您将icons设置为一个具有headeractiveHeader属性的对象。第一个属性指定内容元素关闭时使用的图标,第二个属性指定内容元素打开时使用的图标。我倾向于将这个特性与collapsible设置结合使用,因为当使用图标暗示用户可以执行某个动作时,它会给人一种更自然的感觉。你可以在图 19-18 中看到这些图标是如何出现的。

9781430263883_Fig19-18.jpg

图 19-18 。为折叠部分标题使用自定义图标

使用手风琴的方法

jQuery UI accordion 定义了许多方法,如表 19-9 中所述。

表 19-9 。手风琴方法

方法 描述
accordion("destroy") input元素中删除折叠功能
accordion("disable") 禁用手风琴
accordion("enable") 启用手风琴
accordion("option") 设置一个或多个选项
accordion("refresh") 刷新小工具面板的大小。

image 提示jQuery UI 1.10 中移除了activate方法。请使用我在上一节中描述的active选项。resize方法已被refresh方法取代。

refresh方法更新折叠面板的大小,以反映内容元素的变化。大小变化的影响取决于我在前面部分描述的heightStyle选项的值。其他方法是 jQuery UI 为所有元素提供的方法。

image 提示从 jQuery UI 1.10 开始,调用refresh方法也将更新面板集以反映内容元素的变化,允许添加或删除元素。

使用手风琴事件

jQuery UI accordion 小部件支持表 19-10 中所示的三个事件。

表 19-10 。手风琴比赛

事件 描述
activate 当内容面板被激活时触发
beforeActivate 在内容面板被激活之前触发
create 创建手风琴时触发

image 提示在 jQuery UI 1.10 中,accordion 小部件定义的事件发生了变化。changestart事件被替换为beforeActivate,change事件被替换为activate。传递给这些事件的处理函数的额外的ui对象使用与旧事件不同的属性名,如表 19-11 所述。

表 19-11 。change 和 changestart 事件的 ui 对象的属性

名字 描述
newHeader 新活动内容元素的标题元素
oldHeader 先前活动内容元素的标题元素
newPanel 新活动的内容元素
oldPanel 先前活动的内容元素

您可以使用beforeActivateactive事件来监控内容元素之间的转换,如清单 19-21 所示。

清单 19-21 。使用变更事件

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

        var data = {
            flowers: [{ "name": "Aster", "product": "aster" },
            { "name": "Daffodil", "product": "daffodil" },
            { "name": "Rose", "product": "rose" },
            { "name": "Peony", "product": "peony" },
            { "name": "Primula", "product": "primula" },
            { "name": "Snowdrop", "product": "snowdrop" },
            { "name": "Carnation", "product": "carnation" },
            { "name": "Lily", "product": "lily" },
            { "name": "Orchid", "product": "orchid" }]
        };

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

        $("#accordion").accordion({
            active: false,
            collapsible: true,
            activate: handleAccordionChange
        })

        function handleAccordionChange(event, ui) {
            if (ui.oldHeader.length) {
                console.log("Old header: " + ui.oldHeader[0].innerText);
            }
            if (ui.newHeader.length) {
                console.log("New header: " + ui.newHeader[0].innerText);
            }
        }

        $("button").button();
    });
</script>
...

我使用activate事件来响应被更改的内容元素。jQuery UI 通过处理函数的附加参数将活动元素的信息传递给事件处理函数,就像 autocomplete 小部件一样。这个附加参数通常被命名为ui,定义了表 19-11 中所示的属性。

这些属性是数组,这就是为什么我在第一个索引处获取HTMLElement对象并将innerText属性的值写入控制台之前测试了length属性。

摘要

在本章中,我向您展示了 jQuery UI 自动完成和 accordion 小部件。这些都遵循了我在第十八章中使用的基本模式,但是提供了更丰富的功能和更广泛的配置选项来定制小部件,以便它们可以很好地适应您的 web 应用模型。在第二十章中,我向你展示了标签小工具。

二十、使用选项卡小部件

tabs 小部件表面上类似于我在第十九章中描述的 accordion,但是提供了更多的功能和定制的机会。与前面的小部件章节一样,我从如何创建小部件的细节开始,然后向您展示小部件支持的设置、方法和事件。我用一个例子来结束这一章,这个例子展示了如何使用 tabs 小部件将表单分部分呈现给用户,这是一种处理需要大量数据输入的长表单的有用技术。表 20-1 对本章进行了总结。

表 20-1 。章节总结

问题 解决办法 列表
创建选项卡小部件 定义一个标签和内容元素结构,并调用tabs方法 one
通过 Ajax 获取标签的内容 将选项卡a元素的href属性设置为应该在内容面板中显示的 HTML 文档 2, 3
获取或设置活动选项卡 使用active设置 four
禁用单个选项卡 使用disabled设置 five
更改激活选项卡的事件 使用event设置 six
允许停用所有选项卡 使用collapsible设置 seven
添加或删除选项卡 更改底层 HTML 元素,然后调用refresh方法 eight
强制加载远程内容 使用load方法 nine
在发出 Ajax 请求之前对其进行配置,并在加载时修改远程内容 处理beforeLoadload事件 10, 11
跨多个选项卡显示表单 使用div元素分割表单,添加一个标签结构,并调用tabs方法 12–14
验证显示在多个选项卡中的表单内容 处理beforeActivateactivate事件 Fifteen

自上一版以来,JQUERY UI 发生了变化

在 jQuery UI 1.10 中,tabs 小部件经历了一次重大的 API 变化。方法和配置选项更少,为了与其他小部件的更改保持一致,在进行更改时,更依赖于直接操作小部件所应用的元素。

删除了以下选项:fxajaxOptionscachespinnerselectedidPrefixtabTemplatepanelTemplatecookie。删除了以下方法:urlabortselectaddremovelength。tabs 小部件定义的事件集已经被简化。有关新事件的详细信息,请参见“使用选项卡事件”一节。

这些变化可能看起来很剧烈,但结果是一个更简单、更容易使用的小部件。通过使用 jQuery 操作底层 HTML 元素,然后使用新的refresh方法更新小部件的状态,方法和选项提供的所有功能仍然可用,如本章中的清单所示。

创建选项卡

使用tabs方法创建 jQuery UI 选项卡。和 accordion 小部件一样,tab 小部件需要特定的 HTML 元素结构才能工作,如清单 20-1 所示。

清单 20-1 。创建 jQuery UI 选项卡

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script id="flowerTmpl" type="text/x-jquery-tmpl">
        {{#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 () {
            var data = {
                flowers: [{ "name": "Aster", "product": "aster" },
                { "name": "Daffodil", "product": "daffodil" },
                { "name": "Rose", "product": "rose" },
                { "name": "Peony", "product": "peony" },
                { "name": "Primula", "product": "primula" },
                { "name": "Snowdrop", "product": "snowdrop" },
                { "name": "Carnation", "product": "carnation" },
                { "name": "Lily", "product": "lily" },
                { "name": "Orchid", "product": "orchid" }]
            };

            var elems = $("#flowerTmpl").template(data).filter("*");
            elems.slice(0, 3).appendTo("#tab1");
            elems.slice(3, 6).appendTo("#tab2");
            elems.slice(6).appendTo("#tab3");

            $("#tabs").tabs();
            $("button").button();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="tabs">
            <ul>
                <li><a href="#tab1">Row 1</a>
                <li><a href="#tab2">Row 2</a>
                <li><a href="#tab3">Row 3</a>
            </ul>
            <div id="tab1"></div>
            <div id="tab2"></div>
            <div id="tab3"></div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

应用tabs方法的元素必须包含两种元素。第一个是内容元素,这些元素的内容应该出现在标签中。第二种元素是结构元素,它为 jQuery UI 选项卡小部件提供创建选项卡结构所需的信息。

我使用div元素来提供结构。在下面的例子中,我使用了三个div元素,每个元素保存一行花卉产品信息,就像前面的例子一样:

...
<div id="tab1"></div>
<div id="tab2"></div>
<div id="tab3"></div>
...

重要的是每个内容元素都有一个id属性,以便 jQuery UI 选项卡小部件可以找到要显示的正确元素。对于结构,我使用了li元素,每个元素包含一个a元素,如下:

...
<ul>
    <li><a href="#tab1">Row 1</a>
    <li><a href="#tab2">Row 2</a>
    <li><a href="#tab3">Row 3</a>
</ul>
...

li项的数量定义了标签的数量。a元素的内容被用作选项卡标签,href属性指定该选项卡与哪个内容元素相关。

image 提示我使用数据模板来动态生成选项卡内容,因为它让我更清楚地显示所需的结构。内容可以静态定义,或者像我在下一节中解释的那样,从服务器动态获取。

你可以在图 20-1 中看到例子中的结构是如何转换成一组选项卡的。

9781430263883_Fig20-01.jpg

图 20-1 。创建 jQuery UI 选项卡

标签是一个熟悉的用户界面隐喻。单击一个选项卡会导致 jQuery UI 显示相应的内容元素。与 accordion 一样,tabs 小部件可以让您以相对简洁的方式呈现大量内容,让用户能够专注于对他来说重要的内容。这意味着你必须仔细考虑标签和它们的内容是如何相互关联的。目标应该是将你的内容分组,以减少用户在标签之间切换来找到她想要的内容的次数,同时保留内容元素的自然分组。与任何用户界面一样,这需要对用户正在执行的任务以及她的工作流(而不是你的系统)如何运行有一个坚实的理解。

用 Ajax 获取标签内容

选项卡小部件的一个很好的特性是能够通过 Ajax 获取选项卡内容。为此,您只需指定一个 URL 作为适当的a元素的href属性。通过 Ajax 获取内容的标签被称为远程标签。为了演示这个特性,我创建了一个名为tabflowers.html的 HTML 文档,其内容如清单 20-2 所示。

清单 20-2 。tabflowers.html 文件的内容

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

为了保持示例简单,我使用了与生成的内容元素相同的结构和内容。清单 20-3 展示了如何使用tabflowers.html文件作为标签的内容。

清单 20-3 。通过 Ajax 获取标签的内容

...
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="tabs">
            <ul>
                <li><a href="tabflowers.html">Ajax Content</a>
                <li><a href="#tab1">Row 1</a>
                <li><a href="#tab2">Row 2</a>
                <li><a href="#tab3">Row 3</a>
            </ul>
            <div id="tab1"></div>
            <div id="tab2"></div>
            <div id="tab3"></div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
...

在清单 20-2 中,我添加了一个名为 Ajax Content 的新标签,并指定了应该加载的内容的 URL。你可以在图 20-2 中看到效果。

9781430263883_Fig20-02.jpg

图 20-2 。通过 Ajax 获取标签的内容

image 提示你不需要为远程标签页创建内容元素。这是由选项卡小部件自动完成的。

配置标签窗口小部件

乍一看,tabs 小部件可能看起来像是我在第十九章中向您展示的 accordion 小部件的垂直变体。有一些共同的特征,但是当您使用选项卡时,有一组更广泛的配置选项和设置。表 20-2 描述了 jQuery UI tabs 小部件支持的设置。在接下来的小节中,我将向您展示如何使用这些设置来配置小部件。

表 20-2 。选项卡设置

环境 描述
active 获取或设置当前显示的选项卡;使用基于零的索引来指定单个面板,并且所有面板都可以通过指定false来关闭(但是这仅在collapsible被设置为true时有效)
collapsible 当设置为true时,所有选项卡都关闭
disabled 用于启用/禁用单个选项卡
event 用于设置触发选项卡间转换的事件
heightStyle 指定如何确定小工具及其选项卡的高度
hide 指定标签页关闭时的动画——我在第三十五章中描述了 jQuery UI 动画。
show 指定标签页打开时的动画——我在第三十五章中描述了 jQuery UI 动画。

image 提示在 jQuery UI 1.10 版本中,选项卡小部件的配置选项发生了很大的变化。完整的细节请参见本章开头的注释,但是新的选项集——如表 20-2 所示——更加简单,并且与其他 jQuery UI 小部件更加一致。

选择活动选项卡

active设置支持确定和改变当前显示的标签,如清单 20-4 所示。

清单 20-4 。使用活动选项获取和设置显示的选项卡

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        #radioDiv { text-align: center; margin-top: 10px;}
    </style>
    <script id="flowerTmpl" type="text/x-jquery-tmpl">
        {{#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 () {
            var data = {
                flowers: [{ "name": "Aster", "product": "aster" },
                { "name": "Daffodil", "product": "daffodil" },
                { "name": "Rose", "product": "rose" },
                { "name": "Peony", "product": "peony" },
                { "name": "Primula", "product": "primula" },
                { "name": "Snowdrop", "product": "snowdrop" },
                { "name": "Carnation", "product": "carnation" },
                { "name": "Lily", "product": "lily" },
                { "name": "Orchid", "product": "orchid" }]
            };

            var elems = $("#flowerTmpl").template(data).filter("*");
            elems.slice(0, 3).appendTo("#tab1");
            elems.slice(3, 6).appendTo("#tab2");
            elems.slice(6).appendTo("#tab3");

            $("#tabs").tabs();

            $("#radioDiv").buttonset().change(function (e) {
                $("#tabs").tabs("option", "active", e.target.value);
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="tabs">
            <ul>
                <li><a href="tabflowers.html">Ajax Content</a>
                <li><a href="#tab1">Row 1</a>
                <li><a href="#tab2">Row 2</a>
                <li><a href="#tab3">Row 3</a>
            </ul>
            <div id="tab1"></div>
            <div id="tab2"></div>
            <div id="tab3"></div>
        </div>
        <div id="radioDiv">
            <input type="radio" name="tabNo" id="one" value="1" />
                <label for="one">1</label>
            <input type="radio" name="tabNo" id="two" value="2"/>
                <label for="two">2</label>
            <input type="radio" name="tabNo" id="three" value="3"/>
                <label for="three">3</label>
        </div>
    </form>
</body>
</html>

我在文档中添加了一个 jQuery UI 按钮集,如第十八章中的所述。我使用 jQuery change方法来注册一个处理函数,当单击按钮集中的一个按钮时,这个函数被调用。handler 函数使用option方法设置active属性,产生如图 20-3 中所示的效果。

9781430263883_Fig20-03.jpg

图 20-3 。使用活动设置更改活动选项卡

禁用单个选项卡

如果您使用boolean值,则disabled设置会改变选项卡小部件的状态,但是也可以通过使用一组数字来启用和禁用单个选项卡。清单 20-5 演示了。

清单 20-5 。启用和禁用单个选项卡

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        #buttonDiv {margin: 5px}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {

            $("#tabs").tabs();

            $("input:checkbox").button().click(function () {
                var disabledPositions = [];
                $("input:checked").each(function (index, elem) {
                    disabledPositions.push(Number(elem.value));
                })

                $("#tabs").tabs("option", "disabled", disabledPositions)
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="tabs">
            <ul>
                <li><a href="#tab1">Tab 1</a>
                <li><a href="#tab2">Tab 2</a>
                <li><a href="#tab3">Tab 3</a>
            </ul>
            <div id="tab1">This is the content for tab 1</div>
            <div id="tab2">This is the content for tab 2</div>
            <div id="tab3">This is the content for tab 3</div>
        </div>
        <div id="buttonDiv">
            <label for="cb0">Tab 1</label><input type="checkbox" id="cb0" value=0>
            <label for="cb1">Tab 2</label><input type="checkbox" id="cb1" value="1">
            <label for="cb2">Tab 3</label><input type="checkbox" id="cb2" value="2">
        </div>
    </form>
</body>
</html>

我创建了一个包含静态内容的 tabs 小部件,并添加了一组复选框,我将它们转换成了 jQuery UI 切换按钮。当点击其中一个按钮时,对应的选项卡被启用或禁用,如图图 20-4 所示。

9781430263883_Fig20-04.jpg

图 20-4 。禁用和启用选项卡以响应按钮点击

更改事件触发器

默认情况下,tabs 小部件响应click事件,这意味着用户必须单击一个选项卡来激活它。您可以使用event设置来指定要响应的不同事件。这对于响应鼠标事件非常有用,如清单 20-6 所示。

清单 20-6 。更改激活选项卡的事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css"> #buttonDiv {margin: 5px}</style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#tabs").tabs({
                event: "mouseover"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="tabs">
            <ul>
                <li><a href="#tab1">Tab 1</a>
                <li><a href="#tab2">Tab 2</a>
                <li><a href="#tab3">Tab 3</a>
            </ul>
            <div id="tab1">This is the content for tab 1</div>
            <div id="tab2">This is the content for tab 2</div>
            <div id="tab3">This is the content for tab 3</div>
        </div>
    </form>
</body>
</html>

我在清单中指定了mouseover事件,这意味着当鼠标在选项卡标签上移动时,选项卡小部件将在选项卡之间切换。

image 提示我建议谨慎使用这种方法,就像我对 accordion 小部件的相同设置所做的那样。这在视觉上很吸引人,但是会产生一种令人讨厌的效果,迫使用户注意不要将鼠标从他想要交互的标签上移开。

使用可折叠标签

你可以通过使用collapsible设置来创建一种标签和折叠的混合体,如清单 20-7 所示。

清单 20-7 。使用可折叠设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#tabs").tabs({
            collapsible: true
        });
    });
</script>
...

collapsible设置为true时,单击活动选项卡就像折叠内容元素一样折叠它。你可以在图 20-5 中看到这个过渡。

9781430263883_Fig20-05.jpg

图 20-5 。折叠活动选项卡

image 提示为了完整起见,我已经包括了这个设置,但是我从来没有在我自己的项目中使用过它,因为它的结果会让用户感到困惑。

使用选项卡方法

表 20-3 显示了 jQuery UI tabs 小部件支持的方法。

表 20-3 。选项卡方法

方法 描述
tabs("destroy") 从基础 HTML 元素中移除选项卡小部件
tabs("disable") 禁用整个小部件或单个选项卡(有关使用相应设置的示例,请参见“使用可折叠选项卡”一节)
tabs("enable") 启用整个小部件或单个选项卡
tabs("option") 更改一个或多个设置(有关配置 jQuery UI 小部件的详细信息,请参见第十八章中的“配置按钮”一节)
tabs("load") 使用 Ajax 请求显式加载选项卡的内容
tabs("refresh") 更新小部件以反映底层 HTML 元素的变化

image 提示tabs 小部件支持的方法集在 1.10 版本中发生了变化。删除了以下方法:addremoveselecturllengthabortaddremove方法已经被一个叫做refresh的新方法所取代;通过active设置可以使用select方法的功能;您可以使用新的beforeActivate事件实现功能性abort方法。length方法没有替代功能。

添加和删除选项卡

我使用 jQuery UI tabs 小部件来定义用于操作显示给用户的选项卡集的addremove方法。这些方法在 jQuery UI 1.10 中被删除了,取而代之的是refresh方法,它根据小部件所应用到的底层 HTML 元素的变化来更新选项卡。在清单 20-8 中,你可以看到如何使用刷新方法。

清单 20-8 。使用 refresh 方法更新选项卡小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        #buttons {margin: 5px 0}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {

            $("#tabs").tabs();

            $("button").button().click(function (e) {
                var tabsElem = $("#tabs");
                if (this.id == "add") {
                    var tabID = tabsElem.children("div").length + 1;
                    tabsElem.children("ul").append($("<li>").append($("<a>")
                        .attr("href", "#tab" + tabID).text("Tab " + tabID)));
                    $("<div>").attr("id", "tab" + tabID)
                        .text("This is the content for tab " + tabID).appendTo(tabsElem);
                } else {
                    tabsElem.find("li").first().remove();
                    tabsElem.children("div").first().remove();
                }
                tabsElem.tabs("refresh");
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="buttons" class="ui-widget">
        <button id="add">Add Tab</button>
        <button id="remove">Remove Tab</button>
    </div>
    <div id="tabs">
        <ul>
            <li><a href="#tab1">Tab 1</a>
            <li><a href="#tab2">Tab 2</a>
            <li><a href="#tab3">Tab 3</a>
        </ul>
        <div id="tab1">This is the content for tab 1</div>
        <div id="tab2">This is the content for tab 2</div>
        <div id="tab3">This is the content for tab 3</div>
    </div>
</body>
</html>

我添加了一对button元素,用于添加和删除选项卡小部件使用的lidiv元素。当点击Add Tab按钮时,我会生成新元素,并将它们添加到文档对象模型(DOM)中。当点击Remove Tab按钮时,我移除我能找到的第一个lidiv元素。

在做了这些更改后,我调用了refresh方法,通知 tabs 小部件它应该自我更新以反映我所做的更改,创建了如图 20-6 所示的效果。

9781430263883_Fig20-06.jpg

图 20-6 。添加和删除选项卡

触发选项卡 Ajax 请求

默认情况下,当用户打开包含远程内容的选项卡时,tabs 小部件只会对该选项卡发出 Ajax 请求。这种方法可以防止请求可能永远不会显示的内容,但是会导致用户打开标签时的延迟。您可以告诉 tabs 小部件使用load方法显式加载远程标签的内容,如清单 20-9 所示。

清单 20-9 。使用 load 方法显式获取远程内容

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        #buttons {margin: 5px 0}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {

            $("#tabs").tabs();

            $("#load").button().click(function (e) {
                var tabsElem = $("#tabs");

                tabsElem.find("a").each(function (index, elem) {
                    if (elem.href.indexOf("example.html") == -1) {
                        tabsElem.tabs("load", index);
                    }
                });
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div id="buttons" class="ui-widget">
        <button id="load">Load</button>
    </div>

    <div id="tabs">
        <ul>
            <li><a href="#tab1">Tab 1</a>
            <li><a href="#tab2">Tab 2</a>
            <li><a href="#tab3">Tab 3</a>
            <li><a href="tabflowers.html">Ajax Content</a>
        </ul>
        <div id="tab1">This is the content for tab 1</div>
        <div id="tab2">This is the content for tab 2</div>
        <div id="tab3">This is the content for tab 3</div>
    </div>
</body>
</html>

load方法的参数是您想要加载内容的远程标签的索引。在这种情况下,我使用了load方法来响应被点击的按钮。

使用选项卡事件

jQuery UI tabs 小部件支持的事件集在表 20-4 中描述,我将在接下来的章节中描述它们。

表 20-4 。标签事件

事件 描述
create 当 tabs 小部件应用于底层 HTML 元素时触发
beforeActivate 在选项卡面板显示给用户之前触发
activate 向用户显示选项卡面板后触发
beforeLoad 当远程选项卡的内容即将被加载时触发
load 当远程选项卡的内容已加载时触发

image 提示jQuery UI 1.10 版本改变了 tabs 小部件支持的事件集,删除了selectshowaddremoveenabledisable事件,增加了beforeActivateactivatebeforeLoad事件。被移除的事件是没有意义的,因为 tabs 小部件现在依赖于底层 HTML 元素的状态(和refresh方法),或者因为可以使用新事件重新创建功能。

拦截 Ajax 请求

在请求远程标签的内容之前,触发beforeLoad事件。处理函数被传递一个 jQuery 事件对象和一个附加对象——通常在处理函数中被赋予名称ui——它定义了表 20-5 中所示的属性。

表 20-5 。传递给 beforeLoad 事件处理函数的附加对象的属性

名字 描述
tab 返回一个包含远程选项卡的选项卡元素的jQuery对象
panel 返回一个包含远程选项卡面板元素的jQuery对象
jqXHR 返回将用于发出 Ajax 请求的jqXHR对象
ajaxSettings 返回一个地图对象,该对象将被传递给发出请求的$.ajax方法

这些属性揭示了 jQuery UI 和 jQuery 之间的紧密集成,通过构建我在第十四章和第十五章中描述的 jQuery Ajax 功能。在清单 20-10 中,您可以看到我如何处理beforeLoad事件来调整 Ajax 请求的设置。

清单 20-10 。处理加载前事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#tabs").tabs({
                beforeLoad: function (e, ui) {
                    ui.ajaxSettings.url = "flowers.html";
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="tabs">
        <ul>
            <li><a href="#tab1">Tab 1</a>
            <li><a href="#tab2">Tab 2</a>
            <li><a href="#tab3">Tab 3</a>
            <li><a href="tabflowers.html">Ajax Content</a>
        </ul>
        <div id="tab1">This is the content for tab 1</div>
        <div id="tab2">This is the content for tab 2</div>
        <div id="tab3">This is the content for tab 3</div>
    </div>
</body>
</html>

我为beforeLoad事件注册的处理函数设置了ui.ajaxSettings对象的url属性,以改变远程选项卡将显示的内容的来源。代替 HTML 元素指定的tabflowers.html文件,将加载flowers.html文件。

操纵远程标签数据

当一个远程标签的数据已经从服务器加载,并且可以用来在内容显示给用户之前操作内容时,触发load事件。处理函数传递了一个 jQuery 事件对象和一个额外的ui对象,该对象定义了表 20-6 中显示的属性。

表 20-6 。传递给加载事件处理函数的附加对象的属性

名字 描述
tab 返回一个包含已加载选项卡元素的jQuery对象
panel 返回一个jQuery对象,该对象包含内容被加载到的元素

注意,ui对象返回的属性都没有直接引用从服务器加载的内容。相反,处理函数可以访问选项卡小部件用于选项卡标题和内容面板的元素。在清单 20-11 中,您可以看到我如何使用这些元素来改变标签的标题并操作从服务器加载的内容。

清单 20-11 。处理选项卡加载事件

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#tabs").tabs({
            load: function (e, ui) {
                ui.tab.find("a").text("Loaded!");
                ui.panel.children().first().remove();
            }
        });
    });
</script>
...

我使用ui.tab属性来定位用于选项卡的a元素,并调用 jQuery text 方法来更改标题。我使用ui.panel属性定位从服务器加载的第一个子内容元素,并将其从 DOM 中移除。你可以在图 20-7 中看到效果。

9781430263883_Fig20-07.jpg

图 20-7 。处理加载事件

image 提示注意,我不必调用refresh方法,尽管我正在修改 tabs 小部件所依赖的 DOM 元素。触发加载事件后,会自动应用刷新。

使用选项卡显示表单

这是一种非常有用的技术,可以让长表单更容易理解,同时让用户感觉到她在表单中前进了多远。首先,清单 20-12 显示了包含我将使用的表单的文档。

清单 20-12 。包含表单的文档

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #tab2 input, #tab3 input {width: 200px; text-align: left}
        #tab1, #tab2, #tab3 {padding: 10px}
        .fl {float: left}
        #buttonDiv {clear: both}
        #tabs, h1 {margin: 10px}
        .regLabel {width: auto}
    </style>
    <script id="flowerTmpl" type="text/x-jquery-tmpl">
        {{#flowers}}
            <div class="dcell ui-widget">
                <img src="{{product}}.png"/>
                <label for="{{product}}">{{name}}:</label>
                <input name="{{product}}" value="0"/>
            </div>
        {{/flowers}}
    </script>
    <script id="detailsTmpl" type="text/x-jquery-tmpl">
        {{#details}}
            <div class="ui-widget">
                <label for="{{name}}">{{name}}:</label>
                <input name="{{name}}" placeholder="{{hint}}"/>
            </div>
        {{/details}}
    </script>
    <script type="text/javascript">
        $(document).ready(function () {

            var data = [{ "name": "Aster", "product": "aster" },
                        { "name": "Daffodil", "product": "daffodil" },
                        { "name": "Rose", "product": "rose" },
                        { "name": "Peony", "product": "peony" }];

            var elems = $("#flowerTmpl").template({ flowers: data }).filter("*");
            elems.slice(0, 2).appendTo("#row1");
            elems.slice(2, 4).appendTo("#row2");

            var detailsData = [{ name: "Name", hint: "Enter your name" },
                    { name: "Street", hint: "Enter your street" },
                    { name: "City", hint: "Enter your city" },
                    { name: "State", hint: "Enter your state" },
                    { name: "Zip", hint: "Enter your zip code" }];

            $("#detailsTmpl").template({ details: detailsData }).filter("*")
                .appendTo("#tab2").clone().appendTo("#tab3")

            $("button").button();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="tabs" class="ui-widget">
            <ul>
                <li><a href="#tab1">1\. Select Products</a>
                <li><a href="#tab2">2\. Your Details</a>
                <li><a href="#tab3">3\. Your Shipping Address</a>
            </ul>
            <div id="tab1">
                <h2>1\. Select Products</h2>
                <div id="row1"></div>
                <div id="row2"></div>
            </div>
            <div id="tab2" class="fl">
                <h2>2\. Your Details</h2>
            </div>
            <div id="tab3" class="fl">
               <h2>3\. Your Shipping Address</h2>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

我在文档中添加了一些额外的内容和结构,以充实早期的示例。花卉产品较少,但是我在文档中添加了一些区域来捕获用户的个人和运输细节。你可以在图 20-8 中看到基本形式。

9781430263883_Fig20-08.jpg

图 20-8 。用于选项卡小部件的多部分表单

该表单没有什么特别之处,只是非常适合与 jQuery UI tabs 小部件一起使用,因为它被整齐地划分为不同的区域,每个区域都可以显示在一个选项卡中。

我已经使用数据模板以编程方式添加了大部分内容,您可以看到我如何使用在前面章节中演示的 jQuery 功能从数据中生成元素,根据需要克隆它们,然后将结果添加到文档中。这并不是使用选项卡显示表单的必要条件,但是在一本关于 jQuery 的书中,我喜欢尽可能多地使用核心特性。

您还可以在图中看到ul元素以及它包含的指向内容元素的链接。我通常会隐藏这个元素,但是我想向您展示 tabs 小部件用于标签的结构的一个很好的副作用。创建一个列表,其中每个列表项都包含一个链接,这意味着单击该链接将跳转到文档的该部分,如果该链接指向另一个文件,则浏览器将导航到该文档。这将帮助我从选项卡标题中获得我想要的行为。

应用选项卡

我准备将 tabs 小部件应用到示例中。清单 20-13 显示了script元素中需要的变化。文档中的任何其他地方都不需要更改。

清单 20-13 。创建选项卡小部件

...
<script type="text/javascript">

    $(document).ready(function () {

        var data = [{ "name": "Aster", "product": "aster" },
                    { "name": "Daffodil", "product": "daffodil" },
                    { "name": "Rose", "product": "rose" },
                    { "name": "Peony", "product": "peony" }];

        var elems = $("#flowerTmpl").template({ flowers: data }).filter("*");
        elems.slice(0, 2).appendTo("#row1");
        elems.slice(2, 4).appendTo("#row2");

        var detailsData = [{ name: "Name", hint: "Enter your name" },
                { name: "Street", hint: "Enter your street" },
                { name: "City", hint: "Enter your city" },
                { name: "State", hint: "Enter your state" },
                { name: "Zip", hint: "Enter your zip code" }];

        $("#detailsTmpl").template({ details: detailsData }).filter("*")
            .appendTo("#tab2").clone().appendTo("#tab3")

        $(".fl").removeClass("fl");
        $("#tabs").tabs().find("h2").remove();

        $("button").button();
    });
</script>
...

我删除了用于定位详细信息和送货地址区域内容的fl类,并删除了用于部分标题的h2元素。然后我调用tabs方法,它使用内容元素作为标签的基础,正如你在图 20-9 中看到的。

9781430263883_Fig20-09.jpg

图 20-9 。将选项卡应用于表单

处理按钮按压

为了使表单更容易使用选项卡填写,我为button元素的click事件注册了一个处理程序。在这个处理程序中,我取消了事件的默认操作,并移动到序列中的下一个选项卡,直到到达最后一个选项卡。此时,单击button将表单提交给服务器。清单 20-14 显示了我对script元素添加的内容。

清单 20-14 。使用提交按钮在表单中前进

...
<script type="text/javascript">

    $(document).ready(function () {

        var data = [{ "name": "Aster", "product": "aster" },
                    { "name": "Daffodil", "product": "daffodil" },
                    { "name": "Rose", "product": "rose" },
                    { "name": "Peony", "product": "peony" }];

        var elems = $("#flowerTmpl").template({ flowers: data }).filter("*");
        elems.slice(0, 2).appendTo("#row1");
        elems.slice(2, 4).appendTo("#row2");

        var detailsData = [{ name: "Name", hint: "Enter your name" },
                { name: "Street", hint: "Enter your street" },
                { name: "City", hint: "Enter your city" },
                { name: "State", hint: "Enter your state" },
                { name: "Zip", hint: "Enter your zip code" }];

        $("#detailsTmpl").template({ details: detailsData }).filter("*")
            .appendTo("#tab2").clone().appendTo("#tab3")

        $(".fl").removeClass("fl");
        $("#tabs").tabs().find("h2").remove();

        $("button").button().click(function (e) {
            var tabsElem = $("#tabs");
            var activeTab = tabsElem.tabs("option", "active");
            if (activeTab < tabsElem.find("ul > li").length -1) {
                tabsElem.tabs("option", "active", activeTab + 1)
                e.preventDefault();
            }
        });
    });
</script>
...

我使用active选项来获取活动选项卡的索引,并使用 jQuery 选择器来获取选项卡标题元素的集合,并计算出有多少个。如果用户没有到达最后一个选项卡,那么我设置active选项来前进到序列中的下一个选项卡。只有当用户不在最后一个选项卡上时,我才调用preventDefault方法,这允许表单在选项卡序列的末尾提交。

执行验证

目前,用户可以直接跳到最后一页并提交表单。为了防止这种情况,我将应用一些基本的表单验证。为了使这个例子简单,我将手动处理验证,但是对于真实的项目,我推荐使用验证插件和我在第十三章中描述的技术。清单 20-15 展示了对script元素的修改,以实现一些基本的验证,并防止用户过早跳到 tab 序列的末尾。

清单 20-15 。防止用户跳过带有一些基本验证的选项卡

...
<script type="text/javascript">

    $(document).ready(function () {

        var data = [{ "name": "Aster", "product": "aster" },
                    { "name": "Daffodil", "product": "daffodil" },
                    { "name": "Rose", "product": "rose" },
                    { "name": "Peony", "product": "peony" }];

        var elems = $("#flowerTmpl").template({ flowers: data }).filter("*");
        elems.slice(0, 2).appendTo("#row1");
        elems.slice(2, 4).appendTo("#row2");

        var detailsData = [{ name: "Name", hint: "Enter your name" },
                { name: "Street", hint: "Enter your street" },
                { name: "City", hint: "Enter your city" },
                { name: "State", hint: "Enter your state" },
                { name: "Zip", hint: "Enter your zip code" }];

        $("#detailsTmpl").template({ details: detailsData }).filter("*")
            .appendTo("#tab2").clone().appendTo("#tab3")

        var activePanel;

        $(".fl").removeClass("fl");
        $("#tabs").tabs({
            beforeActivate: function (e, ui) {
                validatePanel(e, ui.oldPanel);
            },
            activate: function (e, ui) {
                activePanel = ui.newPanel;
            }
        }).find("h2").remove();

        function validatePanel(e, panelElem) {
            var inputElems = panelElem.find("input");
            if (panelElem.attr("id") == "tab1" ?
                sumInputElems(inputElems) : countEmptyOrZeroValues(inputElems)) {
                alert("Validation Problem!");
                e.preventDefault();
            }
        }

        function sumInputElems(inputs) {
            var total = 0;
            inputs.each(function (index, elem) {
                total+= Number($(elem).val());
            });
            return total == 0;
        }

        function countEmptyOrZeroValues(inputs) {
            var count = 0;
            inputs.each(function (index, elem) {
                if (elem.value == null || elem.value == "") {
                    count++;
                }
            });
            return count > 0;
        }

        $("button").button().click(function (e) {
            var tabsElem = $("#tabs");
            var activeTab = tabsElem.tabs("option", "active");
            if (activeTab < tabsElem.find("ul > li").length - 1) {
                tabsElem.tabs("option", "active", activeTab + 1)
                e.preventDefault();
            } else {
                validatePanel(e, activePanel);
            }
        });
    });
</script>
...

我使用了两个选项卡事件来获得我想要的效果。当用户从一个选项卡导航到下一个选项卡时,beforeActivated事件很有用,因为它为我提供了一个对即将关闭的选项卡的内容面板的引用,我通过传递给我的处理函数的第二个参数的属性来访问它,该函数是ui对象,如表 20-7 中所述。

表 20-7 。传递给 beforeActivate 和 Activate 事件处理程序的附加对象的属性

名字 描述
newHeader 新活动选项卡的标题元素
oldHeader 先前活动选项卡的标题元素
newPanel 新活动的内容面板
oldPanel 先前活动的内容面板

我使用oldPanel引用来执行验证,并通过调用事件对象上的preventDefaultAction方法来停止向另一个选项卡的转换(如第九章所述)。

这种方法有一个问题,即当用户在完成最后一个选项卡后单击button元素时,不会触发beforeActivate事件,因为表单被提交给了服务器。出于这个原因,我还处理了activate事件,并存储了一个对显示的新选项卡的引用(我通过ui对象的newPanel属性访问它,该属性还定义了表 20-7 中显示的属性)。在允许表单提交给服务器之前,我使用这个引用来执行验证。

我告诉用户验证错误的方法是调用alert函数并在对话框中显示一条消息,如图 20-10 中的所示。显然,在一个真实的项目中,你可以使用验证插件的概要特性,正如我在第十三章中所描述的。

9781430263883_Fig20-10.jpg

图 20-10 。为响应验证错误而显示的警告框

摘要

在本章中,我向您展示了 jQuery UI tabs 小部件。这个小部件提供了丰富而复杂的功能,可以在多种情况下使用。我发现自己经常使用这个小工具。它是灵活的,完全可定制的,用户通常熟悉有选择地显示包含在单个选项卡中的内容的想法,而其他小部件(如 accordion)并不总是这样。在第二十一章中,我向你展示了 datepicker 小部件。

二十一、使用 Datepicker 小部件

本章重点介绍 jQuery UI datepicker 小部件,它提供了一种方便的机制来帮助用户选择日期。众所周知,从用户那里获取日期信息是有问题的,因为可以用多种格式来表示日期。datepicker 小部件可以让用户更容易地选择日期,以一种更一致、更不容易出错的方式,并使用常规文本input元素。表 21-1 对本章进行了总结。

表 21-1 。章节总结

问题 解决办法 列表
创建弹出 jQuery UI 日期选择器 input元素上使用datepicker方法 one
创建内嵌日期选择器 spandiv元素上使用datepicker方法 Two
指定日期选择器显示的日期 使用defaultDate设置 three
指定当用户选择日期时将被更新的附加元素 使用altField设置 four
更改导致弹出日期选择器出现的操作 使用showOn设置 five
指定在 datepicker 触发器按钮中显示的文本 使用buttonText设置 six
显示图像代替触发按钮 使用buttonImagebuttonImageOnly设置 seven
限制日期选择 使用constrainInputminDatemaxDate设置 8, 9
在日期选择器中显示几个月 使用numberOfMonths设置 10–12
启用下拉菜单来帮助导航到月份和年份 使用changeMonthchangeYear设置 Thirteen
在日期选择器中显示周信息 使用showWeekweekHeader设置 Fourteen
用前几个月和后几个月的日期填充日期网格 使用showOtherMonthsselectOtherMonths设置 Fifteen
在日期选择器的底部显示一个按钮栏 使用showButtonBargotoCurrent设置 Sixteen
向用户显示格式提示 使用appendText设置(或 HTML5 占位符功能) 17, 18
以编程方式获取或设置日期 使用getDatesetDate方法 Nineteen
以编程方式显示或隐藏弹出日期选择器 使用showhide方法 Twenty
响应用户导航到新的月份或年份 使用onChangeMonthYear事件 Twenty-one
响应弹出的日期选择器关闭 使用onClose事件 Twenty-two
本地化日期选择器 使用 jQuery UI i8n 支持 Twenty-three

创建日期选择器

您可以通过两种方式使用日期选择器。最常见的是使用datapicker方法将小部件附加到input元素。input没有立即的视觉变化,但是当元素获得焦点时(因为用户或者从其他元素切换或者单击input字段),datepicker 弹出来帮助用户选择日期。清单 21-1 演示了这个弹出窗口 datepicker 。

清单 21-1 。创建弹出日期选择器

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#datep").datepicker();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <div class="ui-widget">
        <label for="datep">Date: </label><input id="datep"/>
      </div>
    </form>
</body>
</html>

在图 21-1 中,你可以看到聚焦于input元素是如何显示日期选择器的。

9781430263883_Fig21-01.jpg

图 21-1 。当 input 元素获得焦点时,将弹出 datepicker

当显示 datepicker 弹出窗口时,用户可以选择手动输入日期或使用 datepicker 窗口选择日期。当input元素失去焦点或者当用户点击 Enter 或 Escape 键时,datepicker 弹出窗口消失。

创建内嵌日期选择器

使用日期选择器的另一种方法是在内联中使用它。为此,您使用 jQuery 选择一个divspan元素,然后调用datepicker方法。只要基础元素可见,内联 datepicker 就可见。清单 21-2 展示了如何创建一个内嵌的日期选择器 。

清单 21-2 。创建内嵌日期选择器

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left; margin-right: 10px}
        #wrapper > * {float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#inline").datepicker();
        });
    </script>   
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="wrapper" class="ui-widget">
            <label for="datep">Date: </label>
            <input id="datep"/>
            <span id="inline"></span>
        </div>
    </form>
</body>
</html>

在这个例子中,我添加了一个span元素,并将其用作datepicker方法的目标。你可以在图 21-2 中看到效果。

9781430263883_Fig21-02.jpg

图 21-2 。内嵌日期选择器

当您不想使用弹出窗口时,内嵌日期选择器会很有用。在某些应用中,日期非常重要,因此始终显示 datepicker 是有意义的,但在大多数情况下,在需要时隐藏弹出窗口是更明智的做法。隐藏和显示内联日期选择器的问题是,文档的布局必须灵活以适应日期选择器,这可能会导致显示问题。几乎在所有情况下,我发现弹出式日期选择器更有用。

配置日期选择器

如果你以前做过与日期有关的工作,你会明白处理它们是复杂的。这种复杂性反映在 datepicker 小部件支持的大量设置中。在接下来的部分中,我描述了可用于配置 datepicker 的相关设置组。

执行基本配置

表 21-2 描述了执行 datepicker 小工具基本配置的设置。在接下来的小节中,我将向您展示如何使用这些设置。

表 21-2 。基本日期选择器设置

环境 描述
altField 指定将随数据选择更新的附加字段。
buttonImageOnly 指定由buttonImage指定的图像应该包含在img元素中,而不是包含在button中。默认为false
buttonImage 指定用于弹出触发器按钮的图像的 URL。默认情况下不使用。
buttonText 指定弹出触发器按钮的文本。默认为省略号(...)。
defaultDate 设置显示日期选择器时突出显示的日期。
disabled 指定最初是否禁用 datepicker 小工具。默认为false
showOn 指定显示弹出日期选择器的触发器。默认为focus

指定默认日期

最基本的设置也是最有用的设置之一。defaultDate设置指定显示日期选择器时将显示的日期。如果您没有为defaultDate设置提供值,那么将使用当前日期。(当然,这是用户系统定义的日期。时区、日期线和错误配置都可能给用户呈现与您预期不同的日期。)

image 提示该设置仅在input元素没有value属性时使用。如果有,要么是因为您在文档中包含了value属性,要么是因为用户之前已经做出了选择,那么 datepicker 将忽略该设置。

如果你不想要今天的日期,那么你可以从几种不同的格式中选择来表达你想要开始的日期。表 21-3 显示了您可以使用的格式和值的范围。

表 21-3 。默认日期设置的格式和值

值/格式 描述
null 使用当前系统日期。
Date对象 使用由Date对象表示的值。
+days-days 使用从今天起指定天数的日期。例如,+3表示显示三天后的日期,-2表示显示两天前的日期。
+1d +7w -1m +1y 使用相对于今天的日期,表示为未来(d)、周(w)、月(m)和年(y)(+)或过去(-)。正值和负值可以混合在一个日期中,因此 2011 年 11 月 12 日使用的值-1d +1m选择日期 2011 年 12 月 11 日。

清单 21-3 展示了如何使用defaultDate设置来指定未来五年的日期。

清单 21-3 。使用默认日期设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            defaultDate: "+5y"
        });
    });
</script>
...

我在 2013 年 6 月写这一章,你可以在图 21-3 中看到defaultDate设置的+5y值聚焦于 2018 年 6 月的日期选择器。

9781430263883_Fig21-03.jpg

图 21-3 。使用 defaultDate 设置显示未来日期

如示例所示,您可以省略任何不想更改的间隔,这样就可以使用+5y而不是+0d +0w +0m +5y。您可以混合搭配不同时间间隔的负值和正值,以确定您想要的日期。

指定替代元素

altField设置指定了一个input元素,当您选择日期时,该元素将被更新。这是链接input元素和内嵌日期选择器的最简单的方法。清单 21-4 显示了使用altField设置来显示内嵌日期选择器的选择。

清单 21-4 。将 altField 设置与内联 Datepicker 一起使用

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#inline").datepicker({
            altField: "#datep"
        });
    });
</script>
...

在清单 21-4 中,我使用了一个选择器字符串来标识我想要使用的元素,但是altField设置也将接受一个 jQuery 对象或一个文档对象模型(DOM) HTMLElement对象。这个例子的效果是,input元素在我每次使用 datepicker 进行选择时显示日期,如图 21-4 中的所示。

9781430263883_Fig21-04.jpg

图 21-4 。指定日期选取器将更新的输入元素

管理弹出触发器

showOn设置控制向用户显示弹出日期选择器的原因。该设置有三个允许值。

  • focus:当input元素获得焦点时弹出。这是默认设置。
  • button:点击按钮时弹出。
  • both:点击按钮或输入获得焦点时弹出。

当您使用buttonboth值时,datepicker 小部件会创建一个button元素,并将其添加到文档中的input元素之后。清单 21-5 显示了showOn设置的使用。

清单 21-5 。使用 showOn 设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            showOn: "both"
        });
    });
</script>
...

你可以在图 21-5 中看到小部件创建的button元素。因为我在这个例子中使用了both值,所以当用户点击button或者聚焦于input元素时,弹出窗口就会显示出来。

9781430263883_Fig21-05.jpg

图 21-5 。为响应 showOn 设置而添加的按钮

image 提示date picker 小部件添加的按钮不是 jQuery UI 按钮小部件。如果你想保持按钮的一致性,那么你需要选择button元素并调用 jQuery UI button方法,如第十八章中的所述。

您可以使用buttonImagebuttonText设置来格式化button元素。如果您将buttonImage设置为 URL,datepicker 小部件将使用button中的图像。或者,您可以使用buttonText设置来设置一个短语来替换默认内容(即...)。清单 21-6 显示了buttonText设置的使用。

清单 21-6 。使用按钮文本设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            showOn: "both",
            buttonText: "Select"
        });
    });
</script>
...

如果同时使用buttonImagebuttonTextOnly设置,可以完全去掉按钮文本。这会导致 datepicker 向文档添加一个img元素,而不是一个按钮。清单 21-7 提供了一个演示。

清单 21-7 。使用图像而不是按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left}
        #dpcontainer * {vertical-align: middle}
        #dpcontainer img {width: 35px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#datep").datepicker({
                showOn: "both",
                buttonImage: "right.png",
                buttonImageOnly: true
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <divid="dpcontainer"class="ui-widget">
        <label for="datep">Date: </label><input id="datep"/>
      </div>
    </form>
</body>
</html>

我指定了一个名为right.png的图像,并将buttonImageOnly 设置为true。我还在文档中添加了一些 CSS 样式来控制图像相对于labelinput元素的外观。datepicker 小部件在如何创建img元素方面不是特别聪明,所以我需要进行补偿,使图像的样式与文档的其余部分保持一致。你可以在图 21-6 中看到有图像而没有按钮的效果。

9781430263883_Fig21-06.jpg

图 21-6 。在弹出日期选择器中使用图像而不是按钮

管理日期选择

您通常希望对用户可以使用 datepicker 小部件选择的日期范围应用约束。表 21-4 描述了允许您应用选择约束来引导用户到您可以处理的日期的设置。

表 21-4 。用于管理日期选择的日期选择器设置

环境 描述
changeMonth true时,日期选择器显示一个下拉菜单,允许直接导航到一个月。默认为false
changeYear true时,日期选择器显示一个下拉菜单,允许直接导航到一个年份。默认为false
constrainInput true时,将input元素中的字符限制为有效日期中包含的字符。默认是true
hideIfNoPrevNext true时,如果相对于显示的时间段,过去或未来没有可选择的日期,则隐藏上一个和下一个按钮,而不是禁用。默认为false
maxDate 指定用户可以选择的最晚日期。默认情况下,允许用户选择任何日期。
minDate 指定用户可以选择的最早日期。默认情况下,允许用户选择任何日期。
numberOfMonths 指定日期选择器显示的月数。默认为1
showCurrentAtPos 指定当前或默认月份在多月日期选择器中的显示位置。默认为0
stepMonths 指定单击“上一个”和“下一个”按钮时显示跳跃的月数。默认为1
yearRange 指定可在通过changeYear设置启用的下拉列表中选择的年份范围。默认显示当前年份的十年前和十年后。

限制输入字符和日期范围

当设置为true时,constrainInput设置将可以输入到input元素中的字符限制为有效日期中包含的字符。字符集取决于您正在使用的本地化配置,我将在“本地化日期选择”一节中对此进行更多讨论如果您没有本地化 datepicker 小部件,那么您可以预期input元素会受到限制,这样用户只能输入数字和/字符。

这个设置并不意味着用户只能输入有效的日期,因为可以输入像99/99/99这样的值,但是它可以帮助减少错误。当showOn设置被设置为button时,该设置的重要性增加,因为当input元素获得焦点时,弹出窗口不会自动出现。用户通常会在出现日期选择器时进行选择,但并不总是意识到按钮会显示选择器。您给用户的每一次直接输入日期的机会都会增加您处理格式错误的值的机会。清单 21-8 显示了constrainInput设置的使用。

清单 21-8 。将基本约束应用于日期选择

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left; margin-right: 10px}
        #wrapper > * {float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#datep").datepicker({
                constrainInput: true,
                minDate: "-3",
                maxDate: "+5"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <div id="wrapper" class="ui-widget">
        <label for="datep">Date: </label><input id="datep"/><span id="inline"></span>
      </div>
    </form>
</body>
</html>

默认情况下,constrainInput设置是true,所以我也为minDatemaxDate设置添加了值,只是为了让这个例子更有趣一点。这些设置允许我指定用户可以选择的最早和最晚日期。与我在“指定默认日期”一节中展示的defaultDate设置一样,我可以将minDatemaxDate设置的日期指定为null(无日期)、一个Date对象、一个number日期或一个相对日期字符串。在清单 21-8 中,我使用了数字选项来指定相对于今天的天数。在图 21-7 中,您可以看到 datepicker 小部件禁用了任何用户不能选择的日期。

9781430263883_Fig21-07.jpg

图 21-7 。限制用户可以选择的日期

image 提示注意,上一页和下一页按钮在不需要时会自动禁用。这些按钮位于日期选择器的左上角和右上角,允许用户移动到上个月和下个月。在图 21-7 中,用户可以选择的所有日期都在当月或上月,因此下一步按钮被禁用。在这种情况下,您可以通过将hideIfNoPrevNext设置为true来隐藏而不是禁用按钮。

minDate不需要在过去,maxDate不需要在未来,并且您不必为两个设置都提供值。如果您需要用户选择一个有某种提前期的日期,您可以为minDate设置指定一个未来的日期,以防止选择您需要准备的日期,如清单 21-9 所示。

清单 21-9 。提供一个日期限制来创建一个延迟窗口

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            minDate: "+7"
        });
    });
</script>
...

在本例中,我已经指定用户不能选择从今天起一周内发生的任何日期。没有maxDate值,这意味着可以选择从现在起一周之后的任何未来日期。你可以在图 21-8 中看到结果。请注意,此图中启用了“下一步”按钮(允许用户导航到下个月),但禁用了“上一步”按钮(因为不允许用户选择过去的日期)。

9781430263883_Fig21-08.jpg

图 21-8 。创建开放式日期选择范围

image 提示minDatemaxDate设置与defaultDate设置协同工作,这意味着您可以指定相对于不是今天的日期的日期范围。

创建多月显示

datepicker 允许您通过numberOfMonths设置指定向用户显示多少个月。您可以指定月数或双元素数组,该数组指定月网格的大小。清单 21-10 显示了基于数组的方法,我发现这种方法最适合内联日期选择器,因为网格通常太大而不能用作弹出窗口(稍后我会解释原因)。

清单 21-10 。使用月数设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#inline").datepicker({
            numberOfMonths: [1, 3]
        });
    });
</script>
...

在这个例子中,我指定了一个高一个月宽三个月的网格。你可以在图 21-9 中看到效果。

9781430263883_Fig21-09.jpg

图 21-9 。显示月份的网格

image 提示二元数组[1, 3]相当于数值3。当您为­numberOfMonths设置提供一个数字时,datepicker 会在一行中显示指定的月数。

我很少在弹出日期选择器中使用这个特性的原因是,大的网格需要假设用户浏览器窗口和显示的大小。datepicker 弹出窗口不是操作系统对话框。它是一个精心格式化的 HTML 元素,作为 HTML 文档的一部分显示。这意味着当一个大的日期选择器显示在一个小屏幕上或一个小浏览器窗口中时,大部分细节被移出了屏幕的边缘。清单 21-11 显示了一个应用于弹出日期选择器的月份网格。

清单 21-11 。使用弹出日期选择器设置月数

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            numberOfMonths: [1, 3]
        });
    });
</script>
...

你可以在图 21-10 中看到结果。不仅许多可用日期对用户隐藏,而且 next 按钮(允许用户推进显示的月份)也不在屏幕上。

9781430263883_Fig21-10.jpg

图 21-10 。显示大的弹出日期选择器

您可以使用showCurrentAtPos设置在多月日期选择器中更改所选日期的位置。从图 21-9 和图 21-10 中可以看到,默认是先显示当前月份,然后是未来的两个月。showCurrentAtPos设置采用一个从零开始的索引值,该值指定当前月份应该显示的位置。如果您需要允许用户选择今天的任意一天,这是一个非常方便的特性。清单 21-12 显示了该设置的使用。

清单 21-12 。使用 showCurrentAtPos 设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#inline").datepicker({
            numberOfMonths: 3,
            showCurrentAtPos: 1
        });
    });
</script>
...

我已经指定当前日期应该显示在日期选择器显示的三个月的中间。你可以在图 21-11 中看到结果。

9781430263883_Fig21-11.jpg

图 21-11 。在多月日期选择器中指定当前月份的位置

提供对月份和年份的直接访问

您可以用下拉菜单替换 datepicker 标题中的月份和年份,这些下拉菜单提供了对月份和年份的直接访问。当有很大的日期范围可供选择时,这对于用户来说是一个有用的快捷方式。控制这些功能的设置是changeMonthchangeYear。这些设置的true值启用相应的菜单,菜单可以彼此独立启用。清单 21-13 展示了这些设置的使用。

清单 21-13 。通过下拉菜单直接访问月份和年份

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            changeMonth: true,
            changeYear: true,
            yearRange: "-1:+2"

        });
    });
</script>
...

在这个清单中,我启用了两个下拉菜单。我还使用了yearRange设置来限制用户可以浏览的年份范围。我指定了一个值-1:+2,这意味着用户可以选择从现在开始的一年到未来的两年。因为我在 2013 年写这一章,所以呈现给用户的年份范围是 2012 年到 2015 年。你可以在图 21-12 中看到菜单是如何显示的(以及年份范围是如何出现的)。

9781430263883_Fig21-12.jpg

图 21-12 。为用户提供对月份和年份的直接访问

image 提示您也可以为yearRange设置提供一个实际年份范围。我可以在图 21-12 中用2012:2015的值得到同样的结果。

管理日期选择器的外观

您可以使用许多设置来定制 datepicker 向用户显示时的外观。对于一般的日期选择目的,您在前面的示例中看到的默认外观通常是合适的,但是调整外观以适应 web 应用的需求的能力非常有用。表 21-5 描述了外观相关的设置。

表 21-5 。用于控制外观的日期选择器设置

环境 描述
appendText 指定将在文档中插入到input元素之后的格式提示。
closeText 指定按钮栏中用于关闭弹出日期选取器的按钮的文本。默认为Done
currentText 指定用于按钮栏中返回到当前日期的按钮的文本。默认为Today
duration 指定执行由showAnim设置指定的动画的速度或持续时间。默认为normal。我在第三十五章中描述了 jQuery UI 动画效果。
gotoCurrent true时,按钮栏中的Today按钮将返回到所选择的日期,而不是今天的日期。默认是false
selectOtherMonths true时,可以选择作为showOtherMonths设置结果显示的日期。默认是false
showAnim 指定用于显示和隐藏弹出日期选择器的动画。我在第三十五章中描述了 jQuery UI 动画效果。默认为show
showButtonPanel true时,日期选择器显示一个按钮栏,允许用户跳转到当前日期,并(当与弹出窗口一起使用时)关闭日期选择器。默认为false
showOptions 指定由showAnim设置定义的动画选项。我在第三十五章中描述了 jQuery UI 动画效果。
showOtherMonths true时,日期选取器用上个月和后续月份的日期填充日期网格中的空白。默认为false
showWeek true时,日期选择器显示一个显示周信息的列。默认为true
weekHeader 设置通过showWeek设置启用的周列标题。默认为Wk

显示周

对于某些应用来说,知道日期属于一年中的哪一周是非常重要的。例如,在预算管理应用中经常会出现这种情况。jQuery UI datepicker 可以显示周信息,通过showWeekweekHeader设置进行配置,如清单 21-14 所示。

清单 21-14 。在日期选择器中显示周信息

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            showWeek: true,
            weekHeader: "Week"
        });
    });
</script>
...

showWeek设置为true时,日期选择器显示一列显示周数。您可以使用weekHeader设置来更改默认的Wk周栏的标题。在这个例子中,我启用了 week 列,并将标题改为Week。你可以在图 21-13 中看到结果。

9781430263883_Fig21-13.jpg

图 21-13 。在日期选择器中显示周信息

允许月份间出血

默认情况下,datepicker 仅显示当月的日期。这意味着在日期范围之前和之后的日期网格中有空白条目。您可以通过使用showOtherMonth设置的值true来显示上个月和下个月的日期,如清单 21-15 所示。

清单 21-15 。让几个月一个月地过去

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            showOtherMonths: true
        });
    });
</script>
...

你可以在图 21-14 中看到结果。除非selectOtherMonths设置为true,否则不能选择其他月份的日期。

9781430263883_Fig21-14.jpg

图 21-14 。显示前几个月和后几个月的日期

使用按钮栏

true时,showButtonBar设置启用日期选择器窗口底部的按钮栏。使用弹出日期选择器时,按钮栏包含TodayDone按钮。Today按钮跳回当前日期,而Done按钮关闭弹出窗口。在图 21-15 中可以看到按钮。当是内嵌日期选择器的一部分时,只显示Today按钮。

9781430263883_Fig21-15.jpg

图 21-15 。显示按钮栏

image 提示您可以使用currentTextcloseText设置更改用于TodayDone按钮的文本。

true时,gotoCurrent设置会将日期选择器返回到当前选择的日期,而不是今天的日期。当您使用defaultDate设置配置了日期选择器时,这很有用。如果日期选择的目的与历史或未来事件相关,则返回当前日期并不总是有意义的。清单 21-16 包含了一个例子。

清单 21-16 。使用 gotoCurrent 设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            showButtonPanel: true,
            gotoCurrent: true,
            defaultDate: "+1m +1y"
        }).val("12/25/2012");
    });
</script>
...

请注意,gotoCurrent设置使按钮转到选定的日期。在清单 21-16 中,日期将取自input元素的value属性,但是如果用户选择了另一个日期,然后再次打开日期选择器,按钮将返回到用户选择的日期,而不是您指定的日期。

向用户提供格式提示

您可以使用appendText设置来为用户提供关于您期望的日期格式的提示。清单 21-17 展示了。

清单 21-17 。使用 appendText 设置提供格式提示

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").datepicker({
            appendText: "(mm/dd/yyyy)"
        });
    });
</script>
...

日期选择器将您指定的文本插入到文档中,如图图 21-16 所示。

9781430263883_Fig21-16.jpg

图 21-16 。使用 appendText 设置向用户提供格式提示

当您依靠按钮来显示弹出的日期选择器时,此设置最有用。当用户不用日期选择器就可以自由输入文本时,那么您给她关于格式的提示可以显著减少您必须处理的错误(这对您来说是好的,对用户来说也不那么令人沮丧)。最近,我开始为input元素使用 HTML5 placeholder属性,作为对日期选择器appendTo设置的一个更简洁的替代。清单 21-18 展示了。

清单 21-18 。使用 HTML5 占位符属性提供格式提示

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#datep").attr("placeholder", "mm/dd/yyyy").datepicker();
    });
</script>
...

显然,这要求用户拥有支持 HTML5 的浏览器,但效果更优雅。用户看到的提示是灰色的文本,当他开始输入时就会消失。我更喜欢这样,因为它将格式提示与input元素更紧密地联系在一起,并且它不需要文档布局中的空间。你可以在图 21-17 中看到占位符是如何在谷歌浏览器中显示的。

9781430263883_Fig21-17.jpg

图 21-17 。使用 HTML5 占位符作为格式提示

使用日期选择器方法

datepicker 小部件支持表 21-6 中所示的方法。

表 21-6 。手风琴方法

方法 描述
datepicker("destroy") 从基础元素中移除 datepicker
datepicker("disable") 禁用日期选择器
datepicker("enable") 启用日期选择器
datepicker("option") 为 datepicker 设置一个或多个选项
datepicker("isDisabled") 如果日期选择器被禁用,则返回true
datepicker("hide") 隐藏弹出式日期选择器
datepicker("show") 显示弹出的日期选择器
datepicker("refresh") 刷新 datepicker 以反映基础元素中的更改
datepicker("getDate") 从 datepicker 获取选定的日期
datepicker("setDate", date) 为日期选择器设置选定的日期

以编程方式获取和设置日期

当我使用多个内联日期选择器来允许用户选择日期范围时,我发现getDatesetDate方法最有用。在这种情况下,我不想在input元素中显示选择的日期。我只想显示第一次和第二次约会之间的天数。清单 21-19 演示了。

清单 21-19 。使用两个日期选择器选择日期范围

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left; margin-right: 10px}
        #wrapper > * {float: left}
        #result {margin: auto; padding: 10px; width: 200px; clear: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#result").hide();

            $("#dateStart, #dateEnd").datepicker({
                minDate: "-7d",
                maxDate: "+7d",
                onSelect: function(date, datepicker) {
                    if (datepicker.id == "dateStart") {
                        $("#dateEnd").datepicker("setDate", date)
                            .datepicker("enable").datepicker("option", "minDate", date)
                    }

                    if (!$("#dateEnd").datepicker("isDisabled")) {
                        var startDate = $("#dateStart").datepicker("getDate");
                        var endDate = $("#dateEnd").datepicker("getDate");
                        var diff = endDate.getDate() - startDate.getDate();
                        $("#dayCount").text(diff).parent().show();
                    }
                }

            }).filter("#dateEnd").datepicker("disable");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <div id="wrapper" class="ui-widget">
        <label for="dateStart">Start: </label><span id="dateStart"></span>
        <label for="dateEnd">End: </label><span id="dateEnd"></span>
      </div>
      <div id="result" class="ui-widget">
        Number of Days: <span id="dayCount"></span>
      </div>
    </form>
</body>
</html>

在清单 21-19 的中有两个日期选择器,第二个在文档第一次加载时被禁用。我使用onSelect事件(当选择日期时触发)来响应用户选择日期。当用户在第一个日期选择器中做出选择时,我使用setDate方法准备第二个日期选择器,使用getDate方法从两个日期选择器中获取日期,以便计算出第一个和第二个选择的日期之间的天数(为了使这个例子简单,我做了一个比较,假设两个日期在同一个月)。您可以在图 21-18 的中看到该文档是如何在浏览器中显示的。

9781430263883_Fig21-18.jpg

图 21-18 。使用 getDate 和 setDate 方法

以编程方式显示和隐藏弹出日期选择器

您可以使用showhide方法以编程方式控制弹出日期选择器在屏幕上的出现。如果您希望将 datepicker 与除了 datepicker 小部件创建的input元素或button的焦点之外的其他东西相关联,这将非常有用。我不太喜欢让日期选择器在文档中创建一个按钮,所以我偶尔会发现自己使用这些方法从自己添加的按钮中控制日期选择器,如清单 21-20 所示。

清单 21-20 。使用显示和隐藏方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left; margin-right: 10px}
        #wrapper > * {float: left}
        label {padding: 4px; text-align: right; width: auto}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#datep").datepicker();

            $("button").click(function(e) {
                e.preventDefault();
                $("#datep").datepicker("show");
                setTimeout(function() {
                    $("#datep").datepicker("hide");
                }, 5000)
            })

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <div id="wrapper" class="ui-widget">
        <label for="datep">Date: </label><input id="datep"/><span id="inline"></span>
        <button>Datepicker</button>
      </div>
    </form>
</body>
</html>

当按钮被点击时,我调用 datepicker show方法。我不经常使用hide方法,因为我希望用户在做出选择后能够关闭弹出窗口,但是为了完整起见,我使用了setTimeout函数,这样在按钮被按下 5 秒后弹出窗口就会被关闭。

使用 Datepicker 事件

像所有 jQuery UI 小部件一样,datepicker 支持一组事件,允许您接收重要更改的通知。表 21-7 描述了这些事件。

表 21-7 。日期选择器事件

事件 描述
create 创建 datepicker 时触发
onChangeMonthYear 当用户移动到不同的月份或年份时触发
onClose 关闭弹出日期选择器时触发
onSelect 当用户选择日期时触发

我不打算再次演示onSelect方法,因为我已经在几个例子中使用过它,包括“以编程方式获取和设置日期”一节中的例子传递给该事件处理函数的参数是所选日期和触发该事件的 datepicker 的字符串表示形式。

响应月份或年份的变化

onChangeMonthYear事件允许您在用户选择新的月份或年份时做出响应,无论是通过changeMonthchangeYear设置启用的下拉菜单,还是通过上一个和下一个按钮。清单 21-21 展示了如何使用这个事件来保持两个日期选择器的一致性。

清单 21-21 。使用 onChangeMonthYear 事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left; margin-right: 10px}
        #wrapper > * {float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#dateStart, #dateEnd").datepicker({
                onSelect: function(date, datepicker) {
                    if (datepicker.id == "dateStart") {
                        $("#dateEnd").datepicker("setDate", date)
                    }
                },
                onChangeMonthYear: function(year, month, datepicker) {
                    if (datepicker.id == "dateStart") {
                        var newDate = new Date();
                        newDate.setMonth(month -1);
                        newDate.setYear(year);
                        $("#dateEnd").datepicker("setDate", newDate);
                    }
                }
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <div id="wrapper" class="ui-widget">
        <label for="dateStart">Start: </label><span id="dateStart"></span>
        <label for="dateEnd">End: </label><span id="dateEnd"></span>
      </div>
    </form>
</body>
</html>

该事件函数的三个参数是显示的年份、显示的月份和触发事件的日期选择器。对于弹出日期选择器,this变量被设置为input元素。当用户在第一个日期选择器上导航到新的月份或年份时,我在第二个日期选择器上设置日期以保持它们同步。

注意,datepicker 小部件将一月表示为一月,而 JavaScript 对象Date使用 0。这就是为什么我要做这样一个丑陋的调整

...
newDate.setMonth(month -1);
...

当我创建我想要在第二个日期选择器中显示的日期时。

响应弹出关闭

您可以使用onClose方法来响应弹出的日期选择器被关闭。即使用户没有选择日期,也会触发此事件。处理函数的参数是日期的字符串表示形式(如果用户没有进行选择就关闭了 datepicker,则为空字符串)和触发事件的 datepicker。清单 21-22 显示了对此事件的简单响应。

清单 21-22 。使用 onClose 事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left; margin-right: 10px}
        #wrapper > * {float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#datep").datepicker({
                onClose: function(date, datepicker) {
                    if (date != "") {
                        alert("Selected: " + date);
                    }
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <div id="wrapper" class="ui-widget">
        <label for="datep">Date: </label><input id="datep"/>
      </div>
    </form>
</body>
</html>

在清单 21-22 中,我向用户显示了一个警告框,提示用户做出选择。我必须承认,我从来没有发现自己在一个真实的项目中使用这个事件;我认为最有用的是onSelect事件。

本地化日期选择

jQuery UI datepicker 全面支持世界各地使用的不同日期格式。要使用它们,您需要将一个额外的 JavaScript 文件导入到您的文档中,并告诉 datepicker 您想要使用哪个区域设置。清单 21-23 提供了一个例子。

清单 21-23 。使用本地化的日期选择器

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <script src="jquery-ui-i18n.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 200px; text-align: left; margin-right: 10px}
        #wrapper > * {float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#inline").datepicker($.datepicker.regional["es"]);
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
      <div id="wrapper" class="ui-widget">
        <label for="datep">Date: </label><input id="datep"/><span id="inline"></span>
      </div>
    </form>
</body>
</html>

jquery-ui-i18n.js文件可以在你在第十七章中创建的自定义 jQuery UI 下载的development-bundle/ui/i18n文件夹中找到。将该文件复制到主 jQuery 和 jQuery UI 脚本文件旁边,并将以下内容添加到文档中:

...
<script src="jquery-ui-i18n.js" type="text/javascript"></script>
...

您可以在创建日期选取器时指定要使用的区域设置,如下所示:

...
$("#inline").datepicker($.datepicker.regional["es"]);
...

这是一种混乱的语法,但是它允许您指定您想要的本地化格式。在本例中,我指定了es,这意味着我将使用西班牙日期格式。你可以在图 21-19 中看到结果。

9781430263883_Fig21-19.jpg

图 21-19 。本地化日期显示

我对本地化的建议是,要么做好,要么干脆不做。这意味着远远超越日期格式,向用户呈现一个完全遵循语言、地址、性别、货币、时间和所有其他当地惯例的界面。如果您只本地化 web 应用的一部分或者不一致地遵循约定,用户会觉得不和谐。为了正确地本地化一个应用,你应该雇佣一个专门从事这项工作的个人或公司。出错的途径太多了,如果没有专业人士的支持,你注定会失败。

如果你发现自己试图使用谷歌翻译来本地化一个应用(这并不罕见),那么我建议你只使用美国英语和美国本地化惯例来交付应用。这将您的客户群限制在那些熟悉拼写、日期、货币等美国变体的人,但至少您将避免在尝试临时本地化时几乎总是出现的火车失事。

摘要

在本章中,我向您展示了 jQuery UI datepicker 小部件的工作原理,您可以使用它来帮助用户选择日期。datepicker 是一个灵活的小部件,允许您定制日期选择的方式以及 datepicker 的外观。我自己使用日期选择器的经验是,当我向用户请求日期信息时,它们对于减少我必须处理的格式问题的数量是非常宝贵的。在第二十二章中,我向你展示了 jQuery UI 对话框和微调控件。

二十二、使用对话框和微调器小部件

在这一章中,我将描述 jQuery UI 对话框和 spinner 小部件。表 22-1 提供了本章的总结。

表 22-1 。章节总结

问题 解决办法 列表
创建 jQuery UI 对话框 选择一个具有title属性的div元素,并调用dialog方法 one
防止对话框一创建就显示 autoOpen设置为false Two
防止用户调整对话框的大小 resizable设置为false three
聚焦于对话框中的一个元素 设置autofocus属性 four
更改对话框的初始位置 使用position设置 five
向对话框中添加一个或多个按钮 使用buttons设置 six
在对话框按钮上显示图标 使用icons设置 seven
允许用户移动对话框 使用draggable设置 eight
创建模型对话框 modal设置为true 9, 10
以编程方式打开和关闭对话框 使用opencloseisOpen方法 Eleven
阻止对话框关闭 beforeClose事件的处理函数中返回false Twelve
响应用户移动或改变对话框的大小 响应dragStartdragStopdragresizeStartresizeStopresize事件 Thirteen
创建一个 jQuery UI 微调器 选择一个input元素并调用spinner方法 14–16
配置基本微调器行为 使用minmaxsteppage设置 Seventeen
更改用于微调按钮的图标 使用icons属性 Eighteen
控制按住微调按钮时的变化率 使用incremental设置 Nineteen
更改微调器编号的格式 使用culturenumberFormat设置 Twenty
以编程方式更改微调器值 调用pageDownpageUpstepDownstepUp方法 Twenty-one
响应微调器值的变化 处理微调器事件 Twenty-two

自上一版以来,JQUERY UI 发生了变化

当我写这本书的第一版时,spinner 小部件还不存在。在 jQuery UI 版本中,对话窗口小部件和 API(应用编程接口)已经更新。有一些新的对话框特性:一个appendTo设置,指定对话框元素在 DOM(文档对象模型)中的位置;支持在对话框元素上设置焦点;position选项已更改为使用我在第十九章的中描述的格式;并且支持在对话框按钮中显示图标。一些功能也被删除了:?? 和 ?? 设置。

使用 jQuery UI 对话框小部件

jQuery UI dialog 小部件创建一个带有标题和内容区域的浮动窗口,类似于您可能在本地应用中看到的那种对话框。对话框有助于将用户的注意力集中在重要的事件或消息上。与任何可能模糊文档内容的元素一样,对话框必须谨慎使用,只有当在文档布局中显示内容不可行时才使用。

创建对话框

对话框小部件是通过选择一个div元素,然后在生成的jQuery对象上调用dialog方法来创建的。对话框小部件需要特定的 HTML 元素结构才能操作,尽管这种结构比选项卡小部件需要的要简单。清单 22-1 显示了一个包含所需元素的文档,它创建了一个对话框。

清单 22-1 。使用 jQuery UI 创建对话框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#dialog").dialog();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dialog" title="Dialog Box">
        This is the content that will be displayed in the dialog box. This <b>content</b>
        can be <em>styled</em>.
    </div>
</body>
</html>

对话框小部件需要一个具有title属性的div元素。该属性的值用作对话框的标题栏。div元素的内容被用作对话框的内容,如清单 22-1 所示,该内容可以包含其他元素。当您在没有设置的情况下调用dialog方法时,正如我在清单中所做的,对话框会立即出现。图 22-1 显示了浏览器如何显示对话框。

9781430263883_Fig22-01.jpg

图 22-1 。简单的对话框

对话框小部件是通过巧妙使用 HTML 元素而不是通过操作系统创建的。这意味着 jQuery UI 对话框的行为方式与本地对话框不同。例如,它不会显示在操作系统的打开窗口列表中,并且可以调整浏览器窗口的大小,以便隐藏部分(或全部)jQuery UI 对话框。

也就是说,jQuery UI 团队在尽可能完善对话框功能方面做得很好。通过单击标题并拖动,用户可以在浏览器窗口中重新定位对话框。该对话框可以调整大小,并可以通过单击右上角的关闭按钮来关闭。而且,由于 jQuery UI 对话框小部件是由 HTML 构建的,它使用我在第十七章的中选择的 jQuery UI 主题进行样式化,并且可以显示复杂和样式化的 HTML 内容。

在详细介绍小部件支持的设置、方法和事件之前,我想演示一下对话框的常见用法。当您调用不带参数的dialog方法时,正如我在第一个例子中所做的,对话框会立即出现,但这通常并不方便。更常见的情况是,您希望在文档加载时创建对话框(以便用户看不到元素结构),然后在以后响应事件时显示对话框。清单 22-2 展示了如何做到这一点。

清单 22-2 。推迟 jQuery UI 对话框的出现

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#dialog").dialog({
                autoOpen: false
            });

            $("button").button().click(function(e) {
                $("#dialog").dialog("open")
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dialog" title="Dialog Box">
      This is the content that will be displayed in the dialog box. This <b>content</b>
      can be <em>styled</em>.
    </div>
    <button>Show Dialog</button>
</body>
</html>

您使用autoOpen设置来防止对话框立即出现。当该设置为false时,HTML 元素结构对用户隐藏,但不显示对话框。当你准备好显示对话框时,你可以调用open方法。你可以在图 22-2 中看到这是如何工作的。

9781430263883_Fig22-02.jpg

图 22-2 。推迟对话框的出现

配置对话框

对话框小部件支持一系列设置,允许您自定义向用户显示对话框的方式。我在上一节向您展示了autoOpen设置,但是还有其他设置,如表 22-2 中所述。

表 22-2 。对话框设置

环境 描述
appendTo 指定对话框应该追加到的元素。
autoOpen true时,对话框在用dialog方法创建后立即显示。默认是true
buttons 指定要添加到对话框中的一组按钮,以及单击这些按钮时将调用的功能。默认情况下不使用按钮。
closeOnEscape true时,按退出键关闭对话框。默认为true
draggable true时,用户可以点击对话框标题并拖动以在浏览器中移动对话框。默认为true
height 以像素为单位指定对话框的初始高度。默认值是auto,它允许对话框自行调整大小。
hide 指定用于隐藏对话框的动画效果。有关 jQuery UI 效果的详细信息,请参见第三十五章。
maxHeight 指定对话框的最大高度(以像素为单位)。默认为false,表示没有大小限制。
maxWidth 指定对话框的最大宽度(以像素为单位)。默认为false,表示没有大小限制。
minHeight 指定对话框的最小高度(以像素为单位)。默认值为false,这意味着没有最小大小限制。
minWidth 指定对话框的最小宽度(以像素为单位)。默认值为false,这意味着没有最小大小限制。
modal true时,对话框是模态的,用户不能与文档交互,直到对话框关闭。默认为false
position 指定对话框的初始位置。
resizable true时,对话框显示一个拖动手柄,允许用户调整对话框的大小。默认为true
show 指定用于显示对话框的动画效果。有关 jQuery UI 效果的详细信息,请参见第三十五章。
title 指定对话框的标题。
width 以像素为单位指定对话框的初始宽度。默认值是auto,它允许对话框自行调整大小。

image 提示 jQuery UI 1.10 增加了appendTo选项,允许指定对话框将要追加到的元素。另一个新特性是对话框也将自动聚焦于内容元素。(我将在本章的后半部分演示这些特性)。其他变化包括指定position设置值的方式的变化,以及删除stackzIndex配置选项。

配置基本对话框外观

title设置允许你从没有title属性的div元素创建一个对话框。如果您无法控制要在对话框中使用的元素的生成,这将非常有用。清单 22-3 显示了title设置的应用。

清单 22-3 。使用标题设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#dialog").dialog({
                title: "Hello",
                resizable: false
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dialog">
      This is the content that will be displayed in the dialog box. This <b>content</b>
      can be <em>styled</em>.
    </div>
</body>
</html>

在这个例子中,我也应用了resizable设置。此设置防止用户调整对话框的大小。我通常将resizable设置为true,因为我喜欢让用户有机会调整对话框的大小以适应内容。你可以在图 22-3 的例子中看到浏览器是如何显示对话框的。

9781430263883_Fig22-03.jpg

图 22-3 。带有自定义标题的对话框,没有调整大小的拖动手柄

对话框将自动聚焦于它找到的第一个具有autofocus属性的内容元素,这在使用对话框小部件请求用户输入时很有用,如清单 22-4 所示。

清单 22-4 。在内容元素上使用自动聚焦属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#dialog").dialog({
                title: "Hello",
                resizable: false
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dialog">
      This is the content that will be displayed in the dialog box. This <b>content</b>
      can be <em>styled</em>.
        <p>Name: <input id="name"/> City: <input id="city" autofocus="autofocus" /></p>
    </div>
</body>
</html>

我已经为对话框的内容添加了input元素,其中第二个元素具有autofocus属性。你可以在图 22-4 中看到对话框组件是如何关注这个元素的。

9781430263883_Fig22-04.jpg

图 22-4 。自动聚焦属性的效果

设置对话框的位置

position设置允许您指定对话框在浏览器窗口中的显示位置。这个位置用我在第十九章中描述的自动完成弹出窗口的相同属性和值来表示,我在表 22-3 中又重复了一遍。

表 22-3 。位置属性

名字 描述
my 指定将用于确定位置的对话框部分
at 指定对话框将相对于其定位的目标元素部分
of 指定对话框将相对于其定位的目标元素;如果省略,这就是body元素
collision 指定当对话框溢出窗口时,应如何调整对话框的位置

使用指定水平和垂直位置的值设置myat属性,用空格分隔。水平值为leftrightcenter,垂直值为topbottomcenter。你可以在清单 22-5 中看到我如何设置这些属性。(你可以在第十九章中了解其他职位属性。)

清单 22-5 。定位对话框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#dialog").dialog({
                title: "Positioned Dialog",
                position: {
                    my: "left top",
                    at: "left top"
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dialog">
      This is the content that will be displayed in the dialog box. This <b>content</b>
      can be <em>styled</em>.
    </div>
</body>
</html>

我已经定位了对话框,使其左上边缘与其父元素(默认情况下是body元素)的左上边缘在一起。你可以在图 22-5 中看到对话框的位置。

9781430263883_Fig22-05.jpg

图 22-5 。指定对话框的位置

向对话框添加按钮

您可以使用buttons设置向 jQuery UI 对话框添加按钮。这个设置的值是一个对象数组,每个对象都有textclick属性。text属性的值被用作按钮的标签,并且您提供了一个函数作为click属性的值,该函数将在按钮被单击时被调用。清单 22-6 显示了这个设置的用法。

清单 22-6 。向对话框添加按钮

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#dialog").dialog({
            title: "Dialog",
            buttons: [{text: "OK", click: function() {/* do something */}},
                      {text: "Cancel", click: function() {$(this).dialog("close")}}]

        });
    });
</script>
...

在这个脚本中,我添加了两个按钮。“确定”按钮的功能不做任何事情,但是对于“取消”按钮,我关闭了对话框。注意,我在 jQuery 选择器的Cancel函数中使用了this变量。这被设置为用于创建对话框的div元素。你可以在图 22-6 中看到按钮是如何显示的。这个例子使用了close方法,它关闭了对话框。我将在本章的后面描述对话方法。

9781430263883_Fig22-06.jpg

图 22-6 。向 jQuery UI 对话框添加按钮

从 jQuery UI 版本 1.10 开始,您可以通过向定义每个按钮的对象添加一个icons属性来为对话框中的按钮指定图标,如清单 22-7 所示。

清单 22-7 。向对话框小部件中的按钮添加图标

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#dialog").dialog({
            title: "Dialog",
            buttons: [{
                text: "OK",
                click: function () {/* do something */ },
                icons: {
                    primary: "ui-icon-star",
                    secondary: "ui-icon-circle-arrow-e"
                }
            },{ text: "Cancel", click: function () { $(this).dialog("close") }}]
        });
    });
</script>
...

icons属性设置为一个对象,该对象定义了名为primarysecondary的属性,这两个属性定义了按钮文本左侧和右侧的图标。这些属性用于以与按钮部件相同的方式指定图标,如第十八章中的所述。你可以在图 22-7 的中看到列表增加的效果。

9781430263883_Fig22-07.jpg

图 22-7 。在对话框按钮中使用图标

image 提示通过在定义按钮的对象(不是icons对象)上定义一个名为showText的属性,并将其设置为false,可以禁用对话框按钮中的文本。

拖动对话框

draggable设置决定了用户是否可以在浏览器窗口中拖动对话框。该设置默认为true,我建议您保持该设置不变。它允许用户看到底层内容。如果您正在显示一个对话框来表达某种错误或问题,这可能特别重要。如果draggable设置为false,那么用户将无法重新定位对话框。

当你在同一个窗口中使用多个对话框时,draggable设置也很重要。我不建议这样做,但是如果你一定要显示对话框,你需要确保用户可以排列它们,以便它们都可以被阅读。在小屏幕上,它们倾向于堆叠在一起,如清单 22-8 所示。

清单 22-8 。使用可拖动设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $(".dialog").dialog({
                draggable: true
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="d1" class="dialog" title="First Dialog">
      This is the first dialog
    </div>
    <div id="d2" class="dialog" title="Second Dialog">
      This is the second dialog
    </div>
    <div id="d3" class="dialog" title="Third Dialog">
      This is the third dialog
    </div>
</body>
</html>

我在这个文档中创建了三个对话框,默认的位置是堆叠在一起的。将draggable设置为true可以移动对话框,如图图 22-8 所示。

9781430263883_Fig22-08.jpg

图 22-8 。拖动对话框

创建模式对话框

一个模态对话框 阻止用户与文档中的元素交互,直到对话框被关闭。modal设置的值true创建一个模态对话框,如清单 22-9 所示。

清单 22-9 。创建一个模态对话框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#dialog").dialog({
                buttons: [{text: "OK", click: function() {$(this).dialog("close")}}],
                modal: true,
                autoOpen: false
            })

            $("#show").button().click(function() {
                $("#dialog").dialog("open");
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dialog" title="Modal Dialog">
      This is a modal dialog. Press OK to continue.
    </div>
    <button id="show">Show Dialog</button>
</body>
</html>

在清单 22-9 中,我创建了一个用户最初看不到的模态对话框。该对话框显示为对点击按钮的响应,你可以在图 22-9 中看到效果。这个例子依赖于显示和关闭对话框的openclose方法。我将在本章后面解释对话框小部件支持的所有方法。

9781430263883_Fig22-09.jpg

图 22-9 。显示模式对话框

当显示一个模态对话框时,jQuery UI 在对话框的后面和文档的前面放置一个暗色层。在对话框关闭之前,文档不会返回到原始状态。在这个例子中,我为用户提供了一个OK按钮来完成这个任务。

image 提示当选择你已经添加到文档中的按钮来显示对话框时,注意不要使用$("button")如果你也在对话框中添加按钮的话。这个选择器匹配你已经添加的按钮那些由dialog方法创建的按钮,这意味着对话框按钮将以与文档中的按钮相同的点击处理程序结束,而不是由buttons设置指定的处理程序。

在模式对话框中显示窗体

模态对话框的好处是它能集中用户的注意力。您可以通过在模态对话框中显示表单来利用这一点,如清单 22-10 所示。

清单 22-10 。在模型对话框中显示表单

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        #dialog input {width: 150px; margin: 5px; text-align: left}
        #dialog label {width: 100px}
        table {border-collapse: collapse; border: thin solid black; margin: 10px}
        #placeholder {text-align: center}
        #show {margin: 10px}
        td, th {padding: 5px; width: 100px}
    </style>
    <script id="rowTmpl" type="text/x-handlebars-template">
        <tr><td>{{product}}</td><td>{{color}}</td><td>{{count}}</td></tr>
    </script>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#dialog").dialog({
                buttons: [{ text: "OK", click: addDataToTable }],
                modal: true,
                autoOpen: false,
                width: 340
            })

            $("#show").button().click(function () {
                $("#dialog").dialog("open");
            })

            function addDataToTable() {
                var data = {
                    product: $("#product").val(),
                    color: $("#color").val(),
                    count: $("#count").val()
                }
                $("#placeholder").hide();
                $("#rowTmpl").template(data).filter("*").appendTo("#prods tbody");
                $("#dialog").dialog("close");
            }
        });
    </script>
</head>
<body>
    <h1>Jacqui"s Flower Shop</h1>
    <div id="dialog" title="Enter Details" class="ui-widget">
        <div><label for="product">Product: </label><input id="product" /></div>
        <div><label for="color">Color: </label><input id="color" /></div>
        <div><label for="count">Quantity: </label><input id="count" /></div>
    </div>
    <table id="prods" class="ui-widget" border="1">
        <tr><th>Product</th><th>Color</th><th>Quantity</th></tr>
        <tr id="placeholder"><td colspan=3>No Products Selected</td></tr>
    </table>
    <button id="show">Add Product</button>
</body>
</html>

在清单 22-10 中,我在div元素中定义了一组input元素,用来创建模态对话框。当用户单击文档中的按钮时,会显示对话框并从用户那里收集数据。当用户单击 OK 按钮时(我使用buttons设置定义了这个按钮),我从input元素中收集值,并使用一个数据模板为 HTML 表格生成一个新行。你可以在图 22-10 中看到顺序。

9781430263883_Fig22-10.jpg

图 22-10 。使用模态对话框捕捉用户的输入

我试图保持这个例子的简单,但是你可以很容易地应用我在第十三章向你展示的验证技术来降低数据错误的风险,并且你可以使用我在第十四章和第十五章向远程服务器提交用户输入的数据。

image 注意在模态对话框中显示一个表单只对简单表单有用。如果您发现自己试图将标签页或折叠页与模态对话框结合在一起,那么您就有混淆和惹恼用户的风险。如果一个表单代表了大量的填写工作,那么它就应该被恰当地集成到文档本身中。

使用对话方法

jQuery UI 对话框小部件支持表 22-4 中描述的方法。

表 22-4 。对话方式

方法 描述
dialog("destroy") 从基础元素中移除对话框小部件
dialog("option") 更改一个或多个设置
dialog("close") 关闭对话框
dialog("isOpen") 如果对话框可见,返回true
dialog("moveToTop") 将对话框移动到堆栈顶部
dialog("open") 向用户显示对话框

如您所料,这些方法中的大多数都允许您以编程方式管理对话框。当然,我发现自己最常用的方法是openclose 。清单 22-11 展示了这些方法。

清单 22-11 。使用对话框方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#d1, #d2").dialog({
                autoOpen: false,
            });

            $("#t1, #t2").button().click(function (e) {
                var target = this.id == "t1" ? "#d1" : "#d2";
                if ($(target).dialog("isOpen")) {
                    $(target).dialog("close")
                } else {
                    $(target).dialog("open")
                }
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="d1" class="dialog" title="First Dialog" class="ui-widget">
        This is the first dialog
    </div>
    <div id="d2" class="dialog" title="Second Dialog" class="ui-widget">
        This is the second dialog
    </div>
    <div>
        <button id="t1">Toggle Dialog 1</button>
    </div>
    <button id="t2">Toggle Dialog 2</button>
</body>
</html>

这个文档包括两个button元素,用于切换两个对话框的可见性。使用isOpen方法评估每个对话框的可见性。图 22-11 显示了浏览器中显示的文档,两个对话框都可见。

9781430263883_Fig22-11.jpg

图 22-11 。使用窗口小部件方法切换对话框的可见性

使用对话框事件

jQuery UI 对话框小部件支持表 22-5 中描述的事件。我将在接下来的章节中描述一些更有用的事件。

表 22-5 。对话事件

事件 描述
create 当对话框小部件应用于底层 HTML 元素时触发
beforeClose 当对话框即将关闭时触发;从处理函数返回false会强制对话框保持打开
open 当对话框打开时触发
focus 当对话框获得焦点时触发
dragStart 当用户开始拖动对话框时触发
drag 拖动对话框时,每次鼠标移动都会触发
dragStop 当用户完成拖动对话框时触发
resizeStart 当用户开始调整对话框大小时触发
resize 调整对话框大小时,每次鼠标移动都会触发
resizeStop 当用户完成调整对话框大小时触发
close 当对话框关闭时触发

保持对话打开

事件允许您接收用户请求关闭对话框的通知。这可能是因为用户按了退出键(如果closeOnEscape设置是true),点击了对话框右上角的关闭图标,或者点击了您通过buttons设置添加的按钮。

大多数情况下,你应该尊重用户的意愿,允许对话框关闭,但也有少数情况下,你需要用户先使用对话框执行一些动作,或者,如清单 22-12 所示,你需要对话框显示一段时间,然后才允许用户继续。

清单 22-12 。阻止对话框关闭

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 150px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            var canClose = false;
            var delay = 15;

            $("#dialog").dialog({
                modal: true,
                autoOpen: false,
                beforeClose: function() {
                    return canClose;
                },
                open: function() {
                    var count = delay;
                    var intID = setInterval(function() {
                        count--;
                        $("#time").text(count);
                        if (count == 0) {
                            clearInterval(intID)
                            canClose = true;
                            $("#dialog").dialog("close")
                        }
                    }, 1000)
                }
            })

            $("button").click(function(e) {
                $("#dialog").dialog("open")
            })

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div class="ui-widget">
        <label for="user">Username: </label><input id="user"/>
        <label for="pass">Password: </label><input id="pass"/>
        <button id="send">Login</button>
    </div>
    <div id="dialog" title="Wrong Password">
        The password you entered was incorrect. Please try again in
        <span id="time">15</span> seconds.
    </div>
</body>
</html>

在清单 22-12 中,我定义了一对input元素来收集用户的用户名和密码。然而,用户输入什么并不重要,因为我通过显示一个错误的密码模式对话框来响应被点击的Login按钮。

我通过启动一个从 15 秒倒计时的重复函数来响应open事件。在此期间,我使用beforeClose事件来阻止用户关闭对话框。在 15 秒结束时,我调用close方法并关闭对话框。通过组合openbeforeClose事件,我可以确保用户不会立即尝试其他用户名或密码组合(至少不会在不重新加载 HTML 文档的情况下尝试)。

响应大小和位置的变化

对话框小部件提供了一组全面的事件,用于在调整对话框大小或拖动对话框时对其进行跟踪。这些是通常不需要的事件,但是在那些能够跟踪变更变得很重要的罕见情况下,这些事件是很方便的。清单 22-13 演示了使用dragStartdragStop事件在对话框被拖动时禁用文档中的inputbutton元素。

清单 22-13 。响应被拖动的对话框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        input {width: 150px; text-align: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#dialog").dialog({
                autoOpen: true,
                dragStart: function() {
                    $("input, #send").attr("disabled", "disabled")
                },
                dragStop: function() {
                    $("input, #send").removeAttr("disabled")
                }
            })

            $("button").click(function(e) {
                $("#dialog").dialog("open")
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div class="ui-widget">
        <label for="user">Username: </label><input id="user"/>
        <label for="pass">Password: </label><input id="pass"/>
        <button id="send">Login</button>
    </div>
    <div id="dialog" title="Wrong Password">
        The password you entered was incorrect. Please try again in
        <span id="time">15</span> seconds.
    </div>
</body>
</html>

使用 jQuery UI 微调器小部件

spinner 小部件是在 1.9 版本中添加到 jQuery UI 中的,它增强了一个带有向上和向下按钮的input元素,这样用户就可以通过一系列值来旋转。使用spinner方法应用微调控件,如清单 22-14 所示。

清单 22-14 。创建基本微调器小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#entry").spinner();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="0" />
    </div>
</body>
</html>

在图 22-12 中可以看到input元素的变换方式,以及点击 jQuery UI 添加到元素中的按钮的效果。

9781430263883_Fig22-12.jpg

图 22-12 。创建和使用微调器

使用带有 HTML5 输入元素类型的微调器小部件

HTML5 支持input元素的 type 属性的一些新值,其中一个与使用 spinner 小部件有相似的效果:类型number。你可以在清单 22-15 中看到我是如何使用这种类型的。

清单 22-15 。使用 HTML5 数字输入元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="0" type="number" />
    </div>
</body>
</html>

我从这个例子中取出了所有的 JavaScript 库和代码,以强调内置浏览器对新类型的input元素的支持。图 22-13 向你展示了谷歌浏览器如何显示这个列表。

9781430263883_Fig22-13.jpg

图 22-13 。使用 Google Chrome 中的数字类型输入元素

这种方法比 jQuery UI 微调器有一些优势,因为更改值的控件的实现不是 HTML5 标准的一部分,这允许浏览器为用户提供特定于设备的导航。jQuery UI 微调器总是使用向上和向下按钮,这在像智能手机这样的小型触摸屏设备上很难使用。

新的number类型的最大问题是大多数浏览器忽略了它,即使是那些实现了其他 HTML5 特性的浏览器。而且很难将原生 HTML5 spinner 与 jQuery UI 混合使用,因为 spinner 小部件在调用 spinner 方法时不会检查input元素的类型。清单 22-16 显示了一个示例 HTML 文档,该文档将微调控件应用于类型为numberinput元素。

清单 22-16 。混合 jQuery UI 小部件和 HTML5 输入元素类型

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#entry").spinner();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="0" type="number" />
    </div>
</body>
</html>

我已经恢复了 JavaScript 库和代码,你可以在图 22-14 中看到效果——两组按钮来改变input元素中的值。新的input元素类型的浏览器实现和 jQuery UI 对它们的处理还不够成熟可靠,我建议避免使用它们,尤其是在组合中。

9781430263883_Fig22-14.jpg

图 22-14 。组合 HTML5 数字输入元素和 jQuery UI 微调器

配置微调器小部件

微调器小部件支持一系列设置,允许您自定义其操作方式,如表 22-6 中所述,并在接下来的章节中演示。

表 22-6 。微调器设置

环境 描述
culture 指定用于分析和格式化数据值的区域设置。
disabled 设置为true.时禁用微调器
icons 指定用于微调按钮的图标(默认图标是上下箭头)。
incremental 指定当按住其中一个按钮时,值如何递增。当设置为true时,按住按钮的时间越长,变化率将增加。当设置为false时,变化率将保持不变。您还可以指定一个函数来自定义速率变化。
max 指定允许的最大值。
min 指定允许的最小值。
numberFormat 指定微调器显示的数字格式。
page 指定页面的大小,这是当使用pageUppageDown方法时值的变化量(将在本章后面描述)。
step 指定单击向上和向下按钮时值的变化量。

配置基本微调器行为

微调小部件的基本行为由四个设置控制:minmaxsteppage。在清单 22-17 中,你可以看到我是如何使用这些属性来约束微调器将支持的值以及它们是如何递增和递减的。

清单 22-17 。配置基本微调器行为

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#entry").spinner({
                min: 2,
                max: 100,
                step: 2,
                page: 5
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="0" />
    </div>
</body>
</html>

minmax属性不强制验证input元素——它们只控制向上和向下按钮使用的值的范围。一旦达到了由max属性定义的限制(例如,按下向上按钮不会再增加值),用户仍然可以直接编辑input元素的内容并插入一个更大的值(或非数值)。

image 提示参见第十三章了解如何对输入元素进行验证的细节

steppage属性控制微调器小部件如何增加和减少数值。step属性指定当用户按下小部件添加的一个按钮时,值的变化量;page属性指定了当pageUppageDown方法被调用时值的变化量(我将在本章后面描述这些方法)。

在清单中,我指定了最小值 2 和最大值 100。我使用step属性告诉 jQuery UI 向上和向下按钮应该将值改变 2,使用page属性告诉 jQuery UI 通过将值增加或减少 5 来响应pageUppageDown方法。你可以在图 22-15 中看到效果。

9781430263883_Fig22-15.jpg

图 22-15 。控制微调器小部件的基本行为

image 提示注意min值不会影响input元素显示的初始值。我已经将input元素上的value属性设置为零,这个值小于我在设置微调器时使用的min值。

如果用户输入一个超出允许范围的值,然后单击向上或向下按钮,微调器小部件会将input元素的内容重置为允许的最小值。你可以在图 22-16 中看到效果。

9781430263883_Fig22-16.jpg

图 22-16 。微调器重置输入元素值

更改微调按钮图标

使用icons属性设置上下按钮显示的图标。默认图标是上下箭头,但是它们可以被设置为任何 jQuery UI 图标(我在第十八章的中描述过),如清单 22-18 所示。

清单 22-18 。改变微调按钮图标

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#entry").spinner({
                min: 2,
                max: 100,
                step: 2,
                page: 5,
                icons: {
                    up: "ui-icon-circle-plus",
                    down: "ui-icon-circle-minus",
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="0" />
    </div>
</body>
</html>

用具有updown属性的对象来设置icons属性,这两个属性定义了将用于相应按钮的图标。我指定了加号和减号图标,你可以在图 22-17 中看到结果。

9781430263883_Fig22-17.jpg

图 22-17 。更改微调器小部件使用的图标

控制变化率

incremental属性用于设置当用户按住updown按钮时值的变化率。将此属性设置为true将逐渐增加按钮被按住的时间越长,值改变的速率越大;值false保持变化率不变。第三个选择是指定一个定制变化率的函数,如清单 22-19 所示。

清单 22-19 。为增量设置属性指定功能

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#entry").spinner({
                incremental: function (spins) {
                    return Math.pow(spins, 2);
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="0" />
    </div>
</body>
</html>

传递给该函数的参数是按钮被按住的持续时间相当于旋转的次数。该函数返回应该显示的值,我的示例返回旋转计数的平方。

设置数字格式

微调器显示的数字格式由numberFormatculture属性控制,并且依赖于一个名为 Globalize 的外部库。

您可以从https://github.com/jquery/globalize获得全球化文件。点击Releases链接,选择你想要的版本(在我写这篇文章的时候,最新的版本是0.1.1,下载一个zip或者tar.gz的档案。从归档文件中提取文件,并将以下文件复制到包含example.html和 JavaScript 库文件的文件夹中。

  • lib/globalize.js
  • lib/cultures/globalize.cultures.js

第一个文件包含用于处理本地化的 JavaScript 代码,第二个文件包含 Globalize 库附带的一组完整的语言环境。

image 提示lib/cultures文件夹中还有其他较小的文件,用于处理单个区域设置,如果您只处理有限的区域设置,可以使用这些文件。

微调小部件的numberFormat属性指定数字的格式,而culture属性指定将使用其格式约定的区域设置。在清单 22-20 中,您可以看到两个属性都在使用。

清单 22-20 。使用数字格式和区域性属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script src="globalize.js"></script>
    <script charset="utf-8" src="globalize.cultures.js"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#entry").spinner({
                culture: "fr-FR",
                numberFormat: "C"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="5.00"/>
    </div>
</body>
</html>

必须在导入globalize.cultures.js文件之前导入globalize.js文件,以便可以向主库注册语言环境信息。在配置微调器时,我将culture设置为fr-FR(这是讲法语的法国的地区指示符),将numberFormat设置为C,这指定了一种货币。你可以在图 22-18 中看到结果。

9781430263883_Fig22-18.jpg

图 22-18 。设置微调器小部件的区域性和格式

image 提示如果省略文化设置,区域设置默认为美国的设置。

法国使用欧元作为其货币,将符号置于数字之后,并用逗号分隔数字的整数部分和小数部分。

注意,我为本地信息设置了script元素的 charset 属性,如下所示:

...
<scriptcharset="utf-8"src="globalize.cultures.js"></script>
...

您可以使用meta元素为 HTML 文档设置字符集,但是如果您还没有这样做——我在示例文档中没有这样做——那么您应该为包含地区信息的script元素显式设置字符集。如果没有这个,大多数浏览器在显示其他地区的货币符号时会有问题,如图图 22-19 所示,它应该显示欧元符号,但却被解释为无意义。

9781430263883_Fig22-19.jpg

图 22-19 。从区域脚本元素中省略字符集属性的效果

使用微调器方法

jQuery UI 微调器小部件支持表 22-7 中描述的方法。

表 22-7 。旋转方法

方法 描述
spinner("destroy") 从基础元素中移除对话框小部件
spinner("disable") 已禁用微调器小部件
spinner("enable") 启用微调器小部件
spinner("option") 获取或设置设置
spinner("pageDown", count) 将微调器值减少指定的页数
spinner("pageUp", count) 将微调器值增加指定的页数
spinner("stepDown", count) 按照指定的步数递减微调器值
spinner("stepUp", count) 按照指定的步数增加微调器值
spinner("value") 获取或设置微调器的当前值

在清单 22-21 中,你可以看到我是如何将特定于 spinner 的方法应用到这个例子中的。

清单 22-21 。使用旋转器方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <script src="globalize.js"></script>
    <script  src="globalize.cultures.js"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        input {width: 150px;}
        button { margin-top: 10px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {

            $("#entry").spinner({
                culture: "en-US",
                numberFormat: "C",
                step: 2,
                page: 10
            });

            $("button").button().click(function () {
                $("#entry").spinner(this.id);
                console.log("Value: " + $("#entry").spinner("value"));
            });

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div class="ui-widget">
        Enter value: <input id="entry" value="5.00"/>
    </div>
    <div>
        <button id="pageDown">Page Down</button>
        <button id="stepDown">Step Down</button>
        <button id="stepUp">Step Up</button>
        <button id="pageUp">Page Up</button>
    </div>
</body>
</html>

我添加了button元素,用来调用微调控件上的方法,根据steppage值递增和递减值。你可以在图 22-20 中看到结果。

9781430263883_Fig22-20.jpg

图 22-20 。以编程方式控制微调器的值

如果查看浏览器控制台,您会看到如下输出:

Value: 8
Value: 10
Value: 12

微调器value方法返回无格式值,不受culturenumberFormat设置的影响。如果您使用 jQuery 选择底层的input元素并使用val方法,您将获得格式化的值。

使用微调器事件

jQuery UI 微调器小部件支持表 22-8 中描述的事件。

表 22-8 。对话事件

事件 描述
create 创建小部件时触发
change 当微调器的值改变并且底层的input元素失去焦点时触发
spin 当值递增或递减时触发
start 在值递增或递减之前触发
stop 值递增或递减后触发

在清单 22-22 中,你可以看到我是如何更新前面的例子来写出微调器的值以响应stop事件的。

清单 22-22 。使用停止事件

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#entry").spinner({
            culture: "en-US",
            numberFormat: "C",
            step: 2,
            page: 10,
            stop: function () {
                console.log("Value: " + $("#entry").spinner("value"));
            }
        });

        $("button").button().click(function () {
            $("#entry").spinner(this.id);
            //console.log("Value: " + $("#entry").spinner("value"));
        });

    });
</script>
...

image 提示您可以通过取消spinstart事件来停止数值的改变。jQuery 对事件的支持详见第九章。

摘要

在本章中,我向您展示了 jQuery UI 对话框和 spinner 小部件。按照与其他小部件章节相同的格式,我将重点放在对话框和微调器小部件支持的设置、方法和事件上。在第二十三章中,我向你展示了菜单和工具提示部件。

二十三、使用菜单和工具提示小部件

在本章中,我将描述 jQuery UI 菜单和工具提示小部件。表 23-1 提供了本章的总结。

表 23-1 。章节总结

问题 解决办法 列表
创建菜单小部件 选择一个元素结构,调用 menu 方法;该结构通常使用ullia元素创建 one
添加分隔符和禁用的菜单项 添加仅包含破折号或空格的元素,并应用ui-state-disabled Two
执行基本导航 向菜单元素结构中的a元素添加href属性 three
使用自定义元素结构 使用menus设置 4, 5
向子菜单添加自定义图标 使用icons设置 six
向菜单项添加图标 添加分配给ui-icon类并指定 jQuery UI 图标的span元素。 seven
设置子菜单的位置 使用position设置 eight
当菜单项处于活动状态并被选中时接收通知 处理blurfocusselect事件 nine
创建工具提示小部件 选择一个具有title属性的元素并调用tooltip菜单 10, 11
设置工具提示的内容 使用contentitems设置 12–15
向工具提示添加自定义样式 使用tooltipClass设置和ui-tooltip-content等级 16, 17
移动工具提示以跟随鼠标 使用track设置 Eighteen
设置工具提示的位置 使用position设置 Nineteen
以编程方式控制工具提示 调用openclose方法 Twenty
工具提示显示和隐藏时接收通知 处理openclose事件 Twenty-one

自上一版以来,JQUERY UI 发生了变化

当我写这本书的第一版时,菜单和工具提示小部件还不存在,作为正在进行的扩展 jQuery UI 小部件范围的努力的一部分,已经添加了这些小部件。

使用 jQuery UI 菜单小部件

菜单小部件——顾名思义——提供一个菜单,允许用户在选项树中导航。这个小部件在向用户呈现具有深层结构的内容时非常有用,比如在线商店的产品类别。

创建菜单

菜单小部件依赖于被表达为 HTML 元素结构的菜单结构,这些元素是用menu方法选择和转换的,如清单 23-1 所示。

清单 23-1 。创建菜单小工具

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        .ui-menu { width: 200px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#menu").menu();
        });
    </script>

</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <ul id="menu">
        <li><a>Bouquets</a></li>
        <li><a>Heirloom Blooms</a></li>
        <li><a>Summer Color</a>
            <ul>
                <li><a>Aster</a></li>
                <li><a>Rose</a></li>
                <li><a>Orchid</a></li>
            </ul>
        </li>
        <li><a>Wedding Classics</a></li>
        <li><a>Exotica</a></li>
    </ul>
</body>
</html>

菜单的结构是使用一个ul元素定义的,其中单个菜单项是包含一个a元素的li元素,菜单项的标签取自a元素的文本。嵌套子菜单可以通过在li元素中定义一个ul元素来创建,你可以在图 23-1 中看到将menu方法应用于列表结构的效果。

9781430263883_Fig23-01.jpg

图 23-1 。创建简单的菜单小部件

在图中,您可以看到我是如何打开一个子菜单并将鼠标放在一个已显示的项目上的。这个例子演示了菜单小部件的两个关键特征。首先,菜单总是可见的——它不是一个弹出菜单,而是一个网页中的永久功能,类似于亚马逊展示其顶级产品类别的方式。

第二,菜单小部件保留了它所应用到的元素的结构——菜单项不按名称或任何其他特征排序。你可以在图 23-1 中看到,排序被保留了下来,包括子菜单的位置。

image 提示注意,我已经使用 CSS 将顶层ul元素的宽度设置为 200 像素。默认情况下,菜单小部件将填充所有可用的水平空间。

格式化菜单项

除了嵌套子菜单之外,元素结构还可以用于格式化菜单项,创建禁用的项目和相关项目组之间的分隔符。清单 23-2 显示了这两种格式。

清单 23-2 。通过元素结构格式化菜单

...
<body>
    <h1>Jacqui's Flower Shop</h1>
    <ul id="menu">
        <li><a>Bouquets</a></li>
        <li><a>Heirloom Blooms</a></li>
        <li>-</li>
        <li><a>Summer Color</a>
            <ul>
                <li><a>Aster</a></li>
                <li><a>Rose</a></li>
                <li><a>Orchid</a></li>
            </ul>
        </li>
        <li><a>Wedding Classics</a></li>
        <li>-</li>
        <li class="ui-state-disabled"><a>Exotica</a></li>
    </ul>
</body>
...

任何内容全是空格或破折号的元素都被解释为菜单分隔符——我在清单中添加了两个这样的元素。给ui-state-disabled类分配一个元素告诉菜单小部件相应的菜单项应该被禁用。你可以在图 23-2 中看到这些添加的结果。

9781430263883_Fig23-02.jpg

图 23-2 。添加分隔符和禁用菜单项

执行基本导航

菜单小部件的一个常见用途是在组成 web 应用的网页之间提供导航,执行这项任务的最简单方法是在支撑菜单小部件的 HTML 元素结构中的a元素上定义href属性。当用户选择一个对应于带有href属性的a元素的菜单项时,浏览器将导航到指定的 URL。在清单 23-3 中,你可以看到我是如何将一个href属性添加到一个菜单项元素中的。

清单 23-3 。向菜单元素添加 href 属性

...
<body>
    <h1>Jacqui's Flower Shop</h1>

    <ul id="menu">
        <li><a>Bouquets</a></li>
        <li><a>Heirloom Blooms</a></li>
        <li>-</li>
        <li><a>Summer Color</a>
            <ul>
                <li><a href="[`apress.com`](http://apress.com">Aster</a></li)">Aster</a></li>
                <li><a>Rose</a></li>
                <li><a>Orchid</a></li>
            </ul>
        </li>
        <li><a>Wedding Classics</a></li>
        <li>-</li>
        <li class="ui-state-disabled"><a>Exotica</a></li>
    </ul>
</body>
...

我给其中一个菜单项添加了一个href属性,这意味着选择Summer ColorAster菜单项将导致浏览器导航到Apress.com

image 提示并不是所有的菜单都用来在网页间导航。当用户通过处理select事件选择一个菜单项时,你可以执行任意的动作,我将在本章后面的使用菜单事件一节中对此进行描述。

配置菜单

菜单小部件支持一系列设置,允许您自定义菜单呈现给用户的方式,如表 23-2 所述。

表 23-2 。菜单设置

环境 描述
disabled 设置为true时,禁用整个菜单
icons 指定要在子菜单上使用的图标
menus 指定用于菜单结构的元素
position 指定子菜单相对于主小工具的位置
role 为可访问性设置自定义 ARIA 角色

使用不同的元素结构

虽然使用ullia元素来定义菜单是标准技术,但是菜单小部件可以处理任何有明确定义的父子关系的元素结构。在清单 23-4 中,你可以看到我是如何使用div元素重新定义示例菜单的。

清单 23-4 。为菜单结构使用不同的元素类型

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        .ui-menu { width: 200px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#menu").menu();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="menu">
        <div><a>Bouquets</a></div>
        <div><a>Heirloom Blooms</a></div>
        <div>-</div>
        <div><a>Summer Color</a>
            <div>
                <div><a>Aster</a></div>
                <div><a>Rose</a></div>
                <div><a>Orchid</a></div>
            </div>
        </div>
        <div><a>Wedding Classics</a></div>
        <div>-</div>
        <div><a>Exotica</a></div>
    </div>
</body>
</html>

当您想用现有的 HTML 元素制作一个菜单时,这是非常有用的,这些元素通常是从模板中生成的,或者是通过 Ajax 从远程服务器获得的。问题是菜单小工具不知道我的哪个div元素代表子菜单,所以我的所有div元素都被当作顶级菜单项,如图图 23-3 所示。

9781430263883_Fig23-03.jpg

图 23-3 。将所有元素视为菜单项的菜单小部件

我可以通过menus属性给菜单小部件关于元素的信息,这个属性被设置为一个选择器,它匹配我想要的子菜单的元素,如清单 23-5 所示。

清单 23-5 。使用菜单配置属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        .ui-menu { width: 200px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#menu").menu({
                menus: "div.subMenu"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div id="menu">
        <div><a>Bouquets</a></div>
        <div><a>Heirloom Blooms</a></div>
        <div>-</div>
        <div><a>Summer Color</a>
            <div class="subMenu">
                <div><a>Aster</a></div>
                <div><a>Rose</a></div>
                <div><a>Orchid</a></div>
            </div>
        </div>
        <div><a>Wedding Classics</a></div>
        <div>-</div>
        <div><a>Exotica</a></div>
    </div>
</body>
</html>

我已经将我想要作为子菜单的div元素分配给了subMenu类,并在创建菜单小部件时为menus属性指定了一个div.subMenu选择器。

image 提示你不必使用一个类,如果你使用了,它也不必被称为subMenu。任何匹配您的元素的选择器都可以工作。

在菜单中使用图标

icons设置指定了菜单部件用于子菜单的图标,并被设置为我在第十八章中描述的一个图标的名称。清单 23-6 显示了icons设置的使用。

清单 23-6 。设置子菜单中使用的图标

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#menu").menu({
            menus: "div.subMenu",
            icons: { submenu: "ui-icon-circle-plus" }
        });
    });
</script>
...

使用定义了submenu属性的对象来设置icons属性,该属性又被设置为您想要使用的图标的名称。在清单中,我指定了一个图标,它在一个圆圈中显示一个加号,你可以在图 23-4 中看到效果。

9781430263883_Fig23-04.jpg

图 23-4 。更改用于子菜单的图标

您可以通过向菜单结构添加span元素并使用class属性来指定图标名称,来为单个菜单项指定图标,如清单 23-7 所示。

清单 23-7 。向菜单项添加图标

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        .ui-menu { width: 200px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#menu").menu({
                icons: { submenu: "ui-icon-circle-plus" }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <ul id="menu">
        <li><a>Bouquets</a></li>
        <li><a>Heirloom Blooms</a></li>
        <li>-</li>
        <li><a>Summer Color</a>
            <ul>
                <li><a>Aster</a></li>
                <li><a>Rose</a></li>
                <li><a>Orchid</a></li>
            </ul>
        </li>
        <li>
            <a><span class="ui-icon ui-icon-circle-check"></span>Wedding Classics</a>
        </li>
        <li>-</li>
        <li class="ui-state-disabled"><a>Exotica</a></li>
    </ul>
</body>
</html>

span元素必须出现在a元素中,并被分配给ui-icon代表要显示的图标的类,在本例中为ui-icon-circle-check。你可以在图 23-5 中看到这种变化的效果。

9781430263883_Fig23-05.jpg

图 23-5 。向菜单项添加图标

定位弹出子菜单

position设置决定了弹出菜单出现在哪里,并且使用了我在第十九章中描述的相同的位置格式。我的建议是允许菜单小部件自动定位子菜单,尤其是因为子菜单的弹出窗口是透明的,当弹出窗口位于父小部件上时,您可以看到底层的菜单结构。在清单 23-8 中,你可以看到用于显示子菜单的position设置,这样它们就位于菜单部件的中心。

清单 23-8 。定位子菜单的弹出窗口

...
<script type="text/javascript">
    $(document).ready(function () {
        $("#menu").menu({
            icons: { submenu: "ui-icon-circle-plus" },
            position: {
                my: "left center",
                at: "center center"
            }
        });
    });
</script>
...

在图 23-6 中可以看到效果,通过弹出菜单可以看到底层菜单项。

9781430263883_Fig23-06.jpg

图 23-6 。将子菜单弹出窗口放置在菜单小工具上

使用菜单方法

jQuery UI 菜单小部件支持表 23-3 中描述的方法。我不认为这些方法有用,因为它们主要提供了在菜单小部件中驱动导航的方法,这种事情最好留给用户去做。这些方法很容易造成不和谐的用户体验,我建议不要使用它们。

表 23-3 。菜单方式

方法 描述
menu("blur") 将焦点从菜单上移开–触发blur事件(将在本章后面描述)
menu("collapse") 关闭当前活动的子菜单
menu("collapseAll") 关闭所有打开的子菜单
menu("destroy") 从基础元素中移除菜单小工具
menu("disable") 禁用菜单
menu("enable") 启用菜单
menu("expand") 打开与当前选定菜单项关联的子菜单
menu("focus") 聚焦于菜单项
menu("isFirstItem") 如果当前选择的项目是菜单中的第一项,则返回true
menu("isLastItem") 如果当前选择的项目是菜单中的最后一项,则返回true
menu("next") 将焦点移到下一个菜单项
menu("option") 更改一个或多个设置
menu("previous") 将焦点移到上一个菜单项
menu("refresh") 更新菜单以反映基础 HTML 元素中的更改
menu("select") 选择活动菜单项,关闭所有打开的子菜单,并触发 select 事件(将在本章后面介绍)

很难演示这些方法,因为菜单小部件的行为与当前选择的菜单项紧密相关。例如,试图用按钮控制一个菜单是行不通的,因为点击按钮会把焦点从菜单上移开。我的建议是避免使用这些方法,而依靠标准的用户交互来处理我在下一节中描述的事件。

image 注意菜单部件还定义了可以滚动的导航菜单的方法,但是在撰写本文时,这个特性还不可靠,所以我从表中省略了这些方法。

使用菜单事件

jQuery UI 菜单小部件支持表 23-4 中描述的事件。

表 23-4 。菜单事件

事件 描述
blur 当菜单失去焦点时触发(该事件可以通过调用blur方法显式触发)
create 创建小部件时触发
focus 当菜单获得焦点和菜单项被激活时触发(该事件可以通过调用focus方法显式触发)
select 当用户选择菜单项或调用 select 方法时触发

在清单 23-9 的中,您可以看到我是如何使用blurfocus事件来跟踪用户激活了哪些菜单项,以及使用select事件来响应用户对菜单项的点击。

清单 23-9 。从菜单小部件处理模糊、聚焦和选择事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        .ui-menu { width: 200px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#menu").menu({
                focus: function (e, ui) {
                    console.log("Focus: " + ui.item.find("a").first().text());
                },
                blur: function () {
                    console.log("Blur");
                },
                select: function (e, ui) {
                    console.log("Select: " + ui.item.find("a").first().text());
                    e.preventDefault();
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <ul id="menu">
        <li><a>Bouquets</a></li>
        <li><a>Heirloom Blooms</a></li>
        <li>-</li>
        <li><a>Summer Color</a>
            <ul>
                <li><a href="http://apress.com">Aster</a></li>
                <li><a>Rose</a></li>
                <li><a>Orchid</a></li>
            </ul>
        </li>
        <li><a>Wedding Classics</a></li>
        <li>-</li>
        <li class="ui-state-disabled"><a>Exotica</a></li>
    </ul>
</body>
</html>

这三个事件的处理函数都被传递了一个 jQuery 事件对象(我在第九章中描述过)和一个额外的ui对象,该对象的item属性返回一个jQuery对象。jQuery对象意味着包含事件对应的 HTML 元素,但是在我写这篇文章的时候,这个属性的值没有为blur事件正确设置。

考虑到这一点,我通过编写与事件相关的元素所包含的第一个a元素的文本内容来处理focusselect事件,并简单地注意到blur事件已经被触发。如果您运行此示例并在菜单中导航,您将看到类似如下的输出:

Focus: Bouquets
Blur
Focus: Heirloom Blooms
Blur
Focus: Summer Color
Select: Summer Color
Blur
Focus: Aster
Blur
Focus: Aster
Select: Aster
Blur
Focus: Aster
Blur

我突出显示了写入控制台的一条语句,因为它演示了包含子菜单的项目本身可以被选择——在这种情况下,我能够单击Summer Color菜单项,即使它包含子菜单。当select方法的处理程序没有考虑到这一点时,这会导致意外的行为。

image 提示注意,我在处理select事件时调用了preventDefault方法,以阻止浏览器导航到由a元素的href属性指定的 URL。

使用 jQuery UI 工具提示小部件

工具提示小部件提供小的弹出窗口,可用于向用户提供有用的上下文信息。工具提示可以是好的或坏的力量。当小心使用时,工具提示可以帮助用户导航一个复杂的过程,但更多时候它们被误用,成为一种干扰或烦恼。

最常见的陷阱是告诉用户一些她已经知道的东西,这样做错过了提供更有用的东西的机会——这在需要复杂数据的 web 表单中最常见。我最近看到的一个例子是在网上报税。税收自然是复杂的,开发人员认为提供工具提示给用户关于每个字段所需数据的信息会很有帮助。这是一种高尚的姿态,但是每个工具提示只是简单地重述了已经从表单标签中明显可见的信息。例如,标签为Birth Date的表单的工具提示告诉我“输入你的出生日期”没有提供的是我必须使用的格式——月/日/年日/月/年年/月/日、等等。你可以在网上看到这个问题的例子,每一个都代表着失去了一个为用户提供有用见解的机会。一个相关的问题是工具提示阻止用户完成任务。每当我在税单的关键字段格式上又猜错一次时,就会出现一个模糊了input元素的工具提示,减缓了我完成这个过程的速度。

我的建议是少用工具提示,仔细考虑它们能给用户带来什么价值。如果你只是简单地重复用户已经拥有的信息,那么重新考虑你的目标。

创建工具提示

jQuery UI tooltip 小部件可以通过tooltip方法应用于任何 HTML 元素,并且默认显示title属性的内容,如清单 23-10 所示。

image 提示主流浏览器无论如何都会使用title属性来显示工具提示,不需要使用 jQuery UI。tooltip 小部件的优势在于,它允许您将工具提示的样式与应用的其他部分保持一致,控制工具提示的使用方式,以及使用更广泛的内容。

清单 23-10 。创建工具提示小工具

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        [title] { font-weight: bold; font-style:italic }
    </style>
    <scripttype="text/javascript">
        $(document).ready(function () {
            $("[title]").tooltip();
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>

    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span title="We are at 200 Main St">20 mile radius</span>
    of the store for free and $1/mile thereafter.</p>
</body>
</html>

在这个例子中,我定义了一个具有title属性的span元素。我选择任何具有title属性的元素并调用tooltip方法,为了强调哪些元素有工具提示,我定义了一个使用相同选择器的 CSS 样式。结果是 jQuery UI 创建了一个工具提示小部件,当鼠标悬停在span元素上时就会显示出来,如图 23-7 中的所示。

9781430263883_Fig23-07.jpg

图 23-7 。jQuery UI tooltip

使用带有输入元素的工具提示

当与它们相关的元素获得焦点时,工具提示也会显示出来——这在处理input元素时很有用,因为当用户在表单中切换时,工具提示会为每个元素出现。对一个input元素应用工具提示和对其他元素一样,如清单 23-11 所示。

清单 23-11 。将工具提示应用于输入元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        input { width: 150px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("input").tooltip();
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>
    <div><label>Name:</label><input title="Use lastname, firstname" /></div>
    <div><label>City:</label><input title="Don't include your state" /></div>
</body>
</html>

在这个清单中有两个带有title属性的input元素,你可以在图 23-8 中看到跳转元素的效果。

9781430263883_Fig23-08.jpg

图 23-8 。对输入元素使用工具提示

配置工具提示

工具提示窗口小部件支持一系列设置,允许您自定义窗口小部件呈现给用户的方式,如表 23-5 所述,并在接下来的章节中演示。

表 23-5 。工具提示设置

环境 描述
content 设置工具提示的内容,可以表示为 HTML 字符串或函数
disabled 设置为true时禁用工具提示
hide 指定隐藏工具提示时如何显示动画——关于 jQuery UI 动画支持的详细信息,参见第三十四章
items 指定可以缩小为其创建工具提示的元素集的选择器
position 指定工具提示相对于其对应元素的位置
show 指定工具提示显示时的动画方式——关于 jQuery UI 动画支持的详细信息,参见第三十四章
tooltipClass 指定工具提示元素将添加到的附加类,允许对不同类型的工具提示(例如,错误)进行样式设置
track 当设置为true时,工具提示的位置会随着鼠标在底层 HTML 元素上的移动而改变

设置工具提示内容

使用 jQuery UI 工具提示的一个很大的优点是,您可以使用它们来显示丰富的内容,这些内容可以指定为 HTML 的一个片段,也可以通过 JavaScript 函数来指定。你可以在清单 23-12 的中看到第一个,这里我使用content属性来指定一个包含格式的 HTML 片段。

清单 23-12 。使用内容设置显示格式化的内容

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        span.toolTip { font-weight: bold; font-style:italic }
    </style>
    <script id="tipContent" type="text/html">
        We are at <b>200</b> Main Street
    </script>
    <script type="text/javascript">
        $(document).ready(function () {
            $("span.toolTip").tooltip({
                content: $("#tipContent").html(),
                items: "span.toolTip"
            });
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>

    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span class="toolTip">20 mile radius</span>
    of the store for free and $1/mile thereafter.</p>
</body>
</html>

我定义了一个新的script元素,但是将type属性设置为text/html——这样可以防止浏览器将script元素的内容视为 JavaScript 或 HTML 内容的一部分。这与用于数据模板的技术是一样的(如第十二章所述),但是我的目标只是在文档的主要部分隐藏一个 HTML 片段,直到我需要它的时候。

我使用content设置将 HTML 片段传递给工具提示小部件。注意,我必须使用 jQuery 选择包含片段的script元素,然后调用html方法来获取其内容——工具提示小部件没有很好地集成到 jQuery 中,只会将 HTML 作为一个字符串。

我还不得不使用items设置——这是因为我不再使用title属性,默认情况下工具提示小部件会查找这个属性。如果没有这个改变,对tooltip方法的调用将忽略我的span元素。我已经将span元素添加到一个名为toolTip的类中,并将它作为我为items设置提供的选择器的基础。

脚本元素中包含的 HTML 片段被用作工具提示的内容,效果如图 23-9 中的所示,包括我通过b元素添加的适度样式。

9781430263883_Fig23-09.jpg

图 23-9 。设置工具提示中内容的样式

image 提示我在这个例子中展示了格式化的文本,但是您可以使用 jQuery UI 工具提示来显示任何 HTML 内容,包括图像。

用函数生成工具提示内容

我也可以使用函数来指定内容。对于本地生成的工具提示内容,我可以直接从函数中返回一个 HTML 片段,如清单 23-13 所示。

清单 23-13 。从函数生成工具提示内容

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        span.toolTip { font-weight: bold; font-style:italic }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("span.toolTip").tooltip({
                content: function () {
                    if (this.id == "address") {
                        return "We are at <b>200</b> Main Street";
                    } else {
                        return "Fee capped at <i>$20</i> during June!"
                    }
                },
                items: "span.toolTip"
            });
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>

    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span id="address" class="toolTip">20 mile radius</span> of the store for free and
    <span id="maxPrice" class="toolTip">$1/mile thereafter.</span></p>
</body>
</html>

在这个例子中,我定义了两个有工具提示的span元素,并使用我提供给content设置的函数为每个工具提示赋予独特的内容。当函数被调用时,this变量的值被设置为触发工具提示内容需求的元素。我使用id属性获取id属性的值,并根据元素返回不同的 HTML 片段。你可以在图 23-10 中看到结果。

9781430263883_Fig23-10.jpg

图 23-10 。使用函数生成工具提示内容

获取远程工具提示内容

还可以使用 Ajax 异步获取工具提示内容,并通过回调向 jQuery UI 提供想要显示的片段。为了演示这是如何工作的,我创建了一个名为tooltips.json的新 JSON 文件,并将它放在与示例中的其他文件相同的目录中。您可以在清单 23-14 中看到tooltips.json文件的内容。

清单 23-14 。tooltips.json 文件的内容

{"address": "We are at <b>200</b> Main Street",
 "maxPrice": "Fee capped at <i>$20</i> during June!"}

在实际的应用中,服务器通常会为给定的元素发回特定的消息,但是为了简单起见,我的 JSON 文件包含了我的示例所需的所有工具提示内容。在清单 23-15 中,您可以看到我如何获得这些数据并使用它们来提供工具提示内容。

清单 23-15 。获取远程工具提示内容

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

        var tooltipData;

        $("span.toolTip").tooltip({
            content: function (callback) {
                if (tooltipData != null) {
                    console.log("Requested serviced locally: " + this.id);
                    return tooltipData[this.id];
                } else {
                    var elemID = this.id;
                    $.getJSON("tooltips.json", function (data) {
                        tooltipData = data;
                        console.log("Requested serviced remotely: " + elemID);
                        callback(tooltipData[elemID]);
                    });
                }
            },
            items: "span.toolTip"
        });
    });
</script>
...

假设 JSON 文件包含我需要的所有值,我可以向服务器发出一个请求,然后存储我为后续工具提示检索的数据——这就是示例中的大部分代码所涉及的内容。这个例子中的关键点是,我传递给getJSON方法的回调(我在第十四章中描述过)直到之后的用于content设置的函数完成后才会执行,这意味着我不能简单地返回工具提示的 HTML 片段作为结果。

相反,我的content函数被传递了一个参数,这是一个当我准备好 HTML 片段时要调用的函数。

 ...
content: function (callback) {
...

当我的 Ajax 请求完成时,我用我想要显示的数据调用这个函数:

...
callback(tooltipData[elemID]);
...

产生的工具提示与前面的例子没有什么区别,但是显示的内容是通过 Ajax 请求获得的。

image 提示为了完整起见,我已经包含了这个特性,但是你应该小心使用它。工具提示在向用户提供即时反馈时效果最好,如果 Ajax 请求需要一段时间才能完成,这种情况可能不会发生。

给工具提示添加额外的 CSS 类

通过tooltipClass设置,您可以指定一个或多个 CSS 类,这些 CSS 类将应用于工具提示以进行自定义样式设置。清单 23-16 提供了一个例子。

清单 23-16 。使用 tooltipClass 设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        span.toolTip { font-weight: bold; font-style:italic }
        *.customTip { border-color: red; }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("span.toolTip").tooltip({
                content: function () {
                    if (this.id == "address") {
                        return "We are at <b>200</b> Main Street";
                    } else {
                        return "Fee capped at <i>$20</i> during June!"
                    }
                },
                items: "span.toolTip",
                tooltipClass: "customTip"
            });
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>
    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span id="address" class="toolTip">20 mile radius</span> of the store for free and
    <span id="maxPrice" class="toolTip">$1/mile thereafter.</span></p>
</body>
</html>

我为名为customTip的类定义了 CSS 样式,并使用tooltipClass设置将该类应用于工具提示。我的样式设置了边框的颜色,结果如图图 23-11 所示。

9781430263883_Fig23-11.jpg

图 23-11 。将自定义类应用于工具提示

jQuery UI 为工具提示生成的 HTML 包括另一个对样式有用的类。以下是为示例中的一个小部件生成的 HTML 示例:

...
<div id="ui-tooltip-3" role="tooltip" class="ui-tooltip ui-widget ui-corner-all
ui-widget-contentcustomTip" style="top: 210.15625px; left: 161px; display: block;">
    <div class="ui-tooltip-content">We are at <b>200</b> Main Street</div>
</div>
...

有一个外部的div元素,由tooltipClass设置指定的类应用于该元素,但是工具提示显示的内容包含在一个内部的div元素中,该元素属于ui-tooltip-content类。对于高级样式,您可以使用这两个类分别针对小部件及其内容,如清单 23-17 所示。

image 提示应用于工具提示的其他类,比如ui-widgetui-corner-all,都是 jQuery UI CSS 框架的一部分,我在第三十五章中描述过。

清单 23-17 。分别设计小部件及其内容的样式

...
<style>
    span.toolTip { font-weight: bold; font-style:italic }
    *.customTip { border-color: red; }
    *.ui-tooltip-content { border: thick solid black; margin: 10px; padding: 10px;
                            background-color: white; }
</style>
...

你可以在图 23-12 中看到这种变化的效果。

9781430263883_Fig23-12.jpg

图 23-12 。独立于工具提示本身更改工具提示内容的样式

image 提示如果你想改变工具提示的背景颜色,你需要在用tooltipClass设置指定的类中将background-image属性设置为none。jQuery UI 小部件背景是使用主题中的图像而不是颜色生成的。

跟踪鼠标

track设置为true时,工具提示将跟随鼠标在工具提示对应的 HTML 元素上的移动。当你想根据鼠标的位置改变工具提示的内容,反映底层 HTML 元素的不同区域时,这很有用,如清单 23-18 所示。

清单 23-18 。用工具提示跟踪鼠标的位置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        span.toolTip { font-weight: bold; font-style:italic }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("span.toolTip").tooltip({
                content: "Move the mouse",
                items: "span.toolTip",
                track: true
            }).mousemove(function(e) {
                $(this).tooltip("option", "content",
                    "X: " + e.pageX + " Y: " + e.pageY);
            });
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>
    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span id="address" class="toolTip">20 mile radius</span> of the store for free and
    <span id="maxPrice" class="toolTip">$1/mile thereafter.</span></p>
</body>
</html>

我使用track设置启用跟踪鼠标,并为mousemove事件设置一个处理函数(如第九章中的所述),该函数使用option方法更新工具提示显示的内容。这是一个你需要亲身经历才能看到正在执行的更新的例子,但是图 23-13 显示了新行为的快照。

9781430263883_Fig23-13.jpg

图 23-13 。用工具提示跟踪鼠标的位置

定位工具提示

position设置指定了工具提示如何相对于它所对应的元素定位,使用了我在第十九章中描述的相同格式。你可以在清单 23-19 中看到一个例子。

清单 23-19 。定位工具提示

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        span.toolTip { font-weight: bold; font-style:italic }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("span.toolTip").tooltip({
                content: function () {
                    if (this.id == "address") {
                        return "We are at <b>200</b> Main Street";
                    } else {
                        return "Fee capped at <i>$20</i> during June!"
                    }
                },
                items: "span.toolTip",
                position: {
                    my: "center bottom",
                    at: "center top"
                }
            });
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>
    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span id="address" class="toolTip">20 mile radius</span> of the store for free and
    <span id="maxPrice" class="toolTip">$1/mile thereafter.</span></p>
</body>
</html>

我已经使用了position设置来指定工具提示应该直接定位在它相关的元素之上,如图 23-14 所示。

9781430263883_Fig23-14.jpg

图 23-14 。定位工具提示

使用工具提示方法

jQuery UI 工具提示小部件支持表 23-6 中描述的方法。

表 23-6 。工具提示方法

方法 描述
tooltip("close") 关闭工具提示(如果它是打开的)
tooltip("destroy") 从基础 HTML 元素中移除工具提示小部件
tooltip("disable") 禁用工具提示,防止其显示
tooltip("enable") 启用工具提示,允许显示它
tooltip("open") 如果工具提示已关闭,则将其打开
tooltip("option") 设置一个配置 选项

值得注意的方法是openclose,它们允许对工具提示进行编程控制。在清单 23-20 中,你可以看到我是如何使用这些方法来显示和隐藏 HTML 文档中包含的所有工具提示的。

清单 23-20 。使用打开和关闭方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        span.toolTip { font-weight: bold; font-style:italic }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("span.toolTip").tooltip({
                content: function () {
                    if (this.id == "address") {
                        return "We are at <b>200</b> Main Street";
                    } else {
                        return "Fee capped at <i>$20</i> during June!"
                    }
                },
                items: "span.toolTip",
                position: {
                    my: "center bottom",
                    at: "center top"
                }
            });
            $("button").button().click(function (e) {
                $("span.toolTip").tooltip(this.id);
            });
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>
    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span id="address" class="toolTip">20 mile radius</span> of the store for free and
    <span id="maxPrice" class="toolTip">$1/mile thereafter.</span></p>
    <div>
        <button id="open">Open</button>
        <button id="close">Close</button>
    </div>
</body>
</html>

你可以在图 23-15 中看到效果。

9781430263883_Fig23-15.jpg

图 23-15 。以编程方式显示和隐藏工具提示

使用工具提示事件

jQuery UI 工具提示小部件支持表 23-7 中描述的事件。

表 23-7 。工具提示事件

事件 描述
close 工具提示关闭时触发
create 应用小部件时触发
open 当工具提示显示时触发

我已经演示了清单 23-21 中的openclose事件。处理函数被传递了一个 jQuery 事件对象(如第九章所述)和一个额外的ui对象,该对象的tooltip属性返回一个包含工具提示元素的jQuery对象。

清单 23-21 。处理打开和关闭事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style>
        span.toolTip { font-weight: bold; font-style:italic }
        span.active { border: medium solid red; padding: 10px; background-color: white }
    </style>
    <script type="text/javascript">
        $(document).ready(function () {
            $("span.toolTip").tooltip({
                content: function () {
                    if (this.id == "address") {
                        return "We are at <b>200</b> Main Street";
                    } else {
                        return "Fee capped at <i>$20</i> during June!"
                    }
                },
                items: "span.toolTip",
                open: function (e, ui) {
                    $(this).toggleClass("active");
                },
                close: function (e, ui) {
                    $(this).toggleClass("active");
                }
            });
        });
    </script>
</head>
<body class="ui-widget">
    <h1>Jacqui's Flower Shop</h1>
    <h3>Color and Beauty to Your Door</h3>
    <p>We are pleased to announce that we are starting a home delivery service for
    your flower needs. We will deliver within a
    <span id="address" class="toolTip">20 mile radius</span> of the store for free and
    <span id="maxPrice" class="toolTip">$1/mile thereafter.</span></p>
</body>
</html>

在这个例子中,我通过在与工具提示相关的元素上切换一个名为active的 CSS 类来处理openclose事件(可通过this变量获得)。效果是当工具提示显示时,该元素被高亮显示,如图图 23-16 所示。

9781430263883_Fig23-16.jpg

图 23-16 。处理打开和关闭事件

摘要

在本章中,我已经向您展示了 jQuery UI 菜单和工具提示小部件是如何工作的。按照与其他小部件章节相同的格式,我将重点放在这些小部件支持的设置、方法和事件上。在第二十四章中,我描述了第一个 jQuery UI 交互。

二十四、使用拖放交互

除了我在第十八章–第二十三章中展示的小部件,jQuery UI 还包括一组交互。这些是较低级别的构建块,允许您向 web 应用界面添加功能。在这一章中,我描述了可拖动的可放下的交互,你可以用它们在 HTML 文档中添加拖放操作。

交互遵循与小部件相同的基本结构。它们有设置、方法和事件。我将遵循相同的模式来描述交互,但是我将稍微跳跃一下,以适应一些交互的独特性质。

使用截屏很难显示应用交互的效果。顾名思义,它们依赖于相互作用。我试图告诉你正在发生的事情的本质,但是要真正理解交互,你应该在浏览器中进行实验。本章中的所有例子(以及其他章节)都包含在 Apress 网站(www.apress.com)的免费源代码/下载区中。表 24-1 对本章进行了总结。

表 24-1 。章节总结

问题 解决办法 列表
应用可拖动交互 使用draggable方法 one
约束元素可以拖动的方向 使用axis设置 Two
限制可以拖动元素的区域 使用containment设置 three
限制拖动到网格中的单元格 使用grid设置 four
延迟拖动一段时间或多个像素 使用delaydistance设置 five
响应被拖动的元素 使用startdragstop事件 six
应用可拖放的交互 使用droppable方法 seven
拖动元素时高亮显示可拖放的元素 使用activatedeactivate事件 eight
当可拖动元素与可放下元素重叠时响应 使用overout事件 nine
指定可拖放元素将接受哪些可拖动元素 使用accept设置 Ten
当拖动开始或重叠时,自动将 CSS 类应用于可拖放的元素 使用activeClasshoverClass设置 Eleven
改变触发over事件的重叠量 使用tolerance设置 Twelve
创建兼容的可拖放元素组 使用scope设置 Thirteen
在拖动期间和之后,将可拖动元素留在原位 使用helper设置 14, 15
操纵 helper 元素以响应可拖放事件 使用ui.helper属性 Sixteen
强制可拖动元素与其他元素的边缘对齐 使用snapsnapModesnapTolerance设置 Seventeen

创建可拖动的交互

应用了draggable交互的元素可以在浏览器窗口中移动(拖动)。该元素正常情况下出现在初始文档布局中,但是如果用户在可拖动元素上按住指针并移动鼠标,该元素的位置会发生变化。清单 24-1 提供了一个可拖动交互的演示。

清单 24-1 。使用可拖动交互

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #draggable {font-size: x-large; border: thin solid black;
            width: 5em; text-align: center}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#draggable").draggable();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="draggable">
        Drag me
    </div>
</body>
</html>

在清单 24-1 中,我选择了一个div元素并调用了draggable方法来创建一个可拖动的元素。如图 24-1 所示,元素从它的常规位置开始,但是可以被拖动到浏览器窗口的任何地方。注意,文档中的另一个元素h1不受draggable方法的影响。

9781430263883_Fig24-01.jpg

图 24-1 。在浏览器窗口中拖动元素

image 提示能够拖动元素本身就很有用,但是当与可拖放的交互结合起来时,它会变得更加强大,我将在本章的后面描述这一点。

可拖动的交互是使用一些巧妙的 HTML 和 CSS 实现的。这意味着它几乎可以在任何浏览器中工作,但也意味着可拖动的元素不能与用户操作系统实现的本地拖放进行交互。

image 提示 HTML5 包括对拖放的支持,通常使用本地操作系统机制来实现。我在我的书HTML5中提供了 html 5 拖放的细节和例子,这本书也是由 Apress 出版的。如果您使用的是 jQuery UI 拖放机制,我建议禁用 HTML5 等价物以避免混淆。为此,将文档中的body元素的draggable属性设置为false

配置可拖动交互

有很多方法可以配置可拖动交互。表 24-2 总结了可用的最重要的设置,我将在接下来的章节中演示。

表 24-2 。可拖动设置

环境 描述
axis 将拖动限制在特定方向。默认为false,表示没有限制,但是您也可以指定x(x 轴)和y(y 轴)。
containment 将可拖动元素限制在屏幕的某个区域。支持的值范围详见表 24-3 。默认为false,表示没有限制。
delay 指定用户在元素移动之前必须拖动元素的持续时间。默认为0,表示没有延迟。
distance 指定用户在元素移动之前必须从其初始位置拖动的距离。默认为1像素。
grid 强制可拖动元素与网格对齐。缺省值是false,意味着不使用网格。

image 提示在本章后面的调整拖动&放下部分,我描述了一些改变可拖动和可放下元素之间关系的附加设置。

约束拖曳轴

有几种方法可以约束元素的拖动方式。第一种是使用axis设置,它允许您将拖动限制在 x 轴或 y 轴。清单 24-2 提供了一个例子。

清单 24-2 。使用轴设置来约束拖动

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.dragElement {font-size: large; border: thin solid black;
            width: 5em; text-align: center; background-color: lightgray; margin: 4px }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $(".dragElement").draggable({
                axis: "x"
            }).filter("#dragV").draggable("option", "axis", "y");

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dragV" class="dragElement">
        Drag Vertically
    </div>
    <div id="dragH" class="dragElement">
        Drag Horizontally
    </div>
</body>
</html>

在清单 24-2 中,我定义了两个div元素,用 jQuery 选择它们,并调用draggable方法。我使用 settings 对象最初为两个div元素定义一个值x,然后使用 jQuery filter方法选择dragV元素,这样我就可以更改y设置,而不必让 jQuery 再次搜索整个文档。结果是一个div元素可以垂直拖动,另一个可以水平拖动。你可以在图 24-2 中看到效果。

9781430263883_Fig24-02.jpg

图 24-2 。约束元素可以拖动的方向

约束拖动区域

您可以限制可以拖动元素的屏幕区域。你可以通过containment设置来实现。如表 24-3 所述,该设置可以使用多种格式进行设置。

表 24-3 。安全壳设置值

价值 描述
选择器 当指定选择器字符串时,可拖动的元素被约束为第一个匹配元素所占据的区域
HTMLElement 可拖动元素被约束到由指定元素占据的区域
string 您可以指定值parentdocumentwindow来限制拖动
数字数组 您可以使用格式为[x1, y1, x2, y2]的数字数组来限制拖动到一个区域

清单 24-3 显示了containment设置的使用。

清单 24-3 。使用密封设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.dragElement {font-size: large; border: thin solid black; padding: 4px;
            width: 5em; text-align: center; background-color: lightgray; margin: 4px }
        #container { border: medium double black; width: 400px; height: 150px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $(".dragElement").draggable({
                containment: "parent"
            }).filter("#dragH").draggable("option", "axis", "x");

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="container">
        <div id="dragH" class="dragElement">
            Drag Horizontally
        </div>
        <div class="dragElement">
            Drag within Parent
        </div>
    </div>
</body>
</html>

在清单 24-3 中,我约束了两个div元素,使它们只能在它们的父元素中拖动,父元素是一个固定大小的div元素。对于其中一个可拖动的div元素,我还应用了axis设置,这意味着它只能在父元素中水平拖动。你可以在图 24-3 中看到结果。

9781430263883_Fig24-03.jpg

图 24-3 。限制拖动到父元素

约束拖动到网格

网格设置可用于使可拖动元素在拖动时与网格对齐。此设置的值是一个双元素数组,以像素为单位指定网格的宽度和高度。清单 24-4 显示了正在使用的网格设置。

清单 24-4 。使用网格设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #draggable {font-size: large; border: thin solid black; padding: 4px;
            width: 100px; text-align: center; background-color: lightgray; margin: 4px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#draggable").draggable({
                grid: [100, 50]
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="draggable">
        Drag Me
    </div>
</body>
</html>

在清单 24-4 中,我指定了一个网格,其中的单元格宽 100 像素,高 50 像素。当你拖动元素时,它会从一个(不可见的)单元格快速移动到下一个,如图图 24-4 所示。

9781430263883_Fig24-04.jpg

图 24-4 。拖动带有网格的元素

捕捉效果很难在屏幕截图中表现出来,这是一个特别受益于交互的例子。

image 提示只需将自由运动轴的值指定为 1,就可以在一个方向上捕捉到网格。例如,设置值[100, 1]强制可拖动元素沿 x 轴对齐 100 个像素单元,但允许沿 y 轴自由移动。

延迟拖动

有两种设置允许您延迟拖动动作。您可以使用delay设置来指定一个时间间隔,这样用户在元素开始移动之前必须拖动元素几毫秒。您也可以使用distance设置,它强制用户在元素开始跟随鼠标之前进行一定数量的像素的拖动动作。清单 24-5 显示了正在使用的两种设置。

清单 24-5 。使用延迟和距离设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #time, #distance {font-size: large; border: thin solid black; padding: 4px;
            width: 100px; text-align: center; background-color: lightgray; margin: 4px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#time").draggable({
                delay: 1000
            })

            $("#distance").draggable({
                distance: 150
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="time">Time Delay</div>
    <div id="distance">Distance</div>
</body>
</html>

在清单 24-5 中我有两个可拖动的div元素,其中一个我用delay设置配置,另一个用distance设置配置。当使用delay时,在元素开始移动之前,用户必须继续拖动指定的毫秒数。在本例中,这是 1000 毫秒。用户不必在这段时间内一直移动鼠标,但必须在整个时间内按住鼠标按钮,并且必须移动鼠标才能开始拖动过程。当时间跨度过去后,可拖动的元素将捕捉到鼠标指针的位置,受我前面展示的网格、区域和轴的约束。

distance设置有类似的效果,但是用户必须将鼠标指针从元素的起点向任意方向移动至少指定数量的像素。当鼠标移动到那个距离时,可拖动的元素将会吸附到当前的鼠标位置。

image

使用可拖动方法

可拖动的交互只定义了您看到的由小部件实现的一组核心方法。没有可拖动的特定方法。表 24-4 描述了那些可用的。

表 24-4 。可拖动方法

方法 描述
draggable("destroy") 从元素中移除交互
draggable("disable") 禁用了可拖动交互
draggable("enable") 启用可拖动交互
draggable("option") 更改一个或多个设置

使用可拖动事件

可拖动交互支持一组简单的事件,当元素被拖动时,这些事件会通知您。表 24-5 描述了这些事件。

表 24-5 。可拖动事件

事件 描述
create 当可拖动交互应用于元素时触发
start 拖动开始时触发
drag 拖动过程中鼠标移动时触发
stop 拖动停止时触发

您可以像处理小部件事件一样响应交互事件。清单 24-6 演示了如何处理startstop事件。

清单 24-6 。使用可拖动的开始和停止事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #draggable {font-size: large; border: thin solid black; padding: 4px;
            width: 100px; text-align: center; background-color: lightgray; margin: 4px; }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#draggable").draggable({
                start: function() {
                    $("#draggable").text("Dragging...")
                },
                stop: function() {
                    $("#draggable").text("Drag Me")
                }
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="draggable">
        Drag Me
    </div>
</body>
</html>

在清单 24-6 中,我使用startstop事件来改变元素被拖动时的内容。这是使用 HTML 和 CSS 实现可拖动交互(以及所有其他 jQuery UI 交互)的好处:即使可拖动元素在屏幕上移动,也可以使用 jQuery 来修改它。你可以在图 24-5 中看到这个例子产生的效果。

9781430263883_Fig24-05.jpg

图 24-5 。使用可拖动事件在元素被拖动时修改元素

使用可拖放的交互

当你把可拖动的交互和可放下的交互结合起来时,它的真正效用就出现了。您使用droppable方法创建可删除的元素,但是要获得真正的功能,您需要为交互定义的事件提供处理函数。表 24-6 描述了可用的事件。

表 24-6 。可丢弃事件

事件 描述
create 应用可拖放交互时触发
activate 当用户开始拖动可拖动元素时触发
deactivate 当用户停止拖动可拖动元素时触发
over 当用户将可拖动元素拖动到可放下元素上时触发(但尚未释放鼠标按钮)
out 当用户将可拖动元素拖出可拖放元素时触发
drop 当用户将可拖动的元素放到可放下的元素上时触发

你可以用drop事件创建一个基本的可删除元素,如清单 24-7 所示。

清单 24-7 。创建一个基本的可拖放交互

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #draggable, #droppable {font-size: large; border: thin solid black; padding: 4px;
            width: 100px; text-align: center; background-color: lightgray; margin: 4px;}
        #droppable {padding: 20px; position: absolute; left: 5px; bottom: 5px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#draggable").draggable();

            $("#droppable").droppable({
                drop: function() {
                    $("#draggable").text("Dropped")
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="droppable">
        Drop Here
    </div>
    <div id="draggable">
        Drag Me
    </div>
</body>
</html>

我添加了一个文本内容为Drop Herediv元素。我使用 jQuery 选择这个元素并调用 droppable 方法,传入一个为drop事件定义处理程序的 settings 对象。为了响应这个方法,我使用text方法更改了可拖动元素的文本。清单 24-7 创建了最乏味的拖放交互,但是它也提供了一个有用的基础来解释可拖动和可放下交互可以一起使用的方式。你可以在图 24-6 中看到这个例子的不同阶段。

9781430263883_Fig24-06.jpg

图 24-6 。使用简单的拖放操作

这是非常基本的东西。我将可拖动的元素拖到可放下的元素上,然后放开。可拖动元素保持在我离开它的地方,并且它的文本内容被改变以响应drop事件。在接下来的小节中,我将向您展示如何使用其他可拖放事件来改善体验。

突出显示拖放目标

当用户开始拖动动作时,您可以使用activatedeactivate事件来突出显示拖放目标。这通常是一个好主意,因为它给用户一个清晰的信号,告诉用户哪些元素是拖放模型的一部分。清单 24-8 提供了一个例子。

清单 24-8 。响应激活和去激活事件

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

        $("#droppable").droppable({
            drop: function() {
                $("#draggable").text("Dropped");
            },
            activate: function() {
                $("#droppable").css({
                    border: "medium double green",
                    backgroundColor: "lightGreen"
                });
            },
            deactivate: function() {
                $("#droppable").css("border", "").css("background-color", "");
            }
        });
    });
</script>
...

当用户开始拖动元素时,我的可拖放元素触发激活事件,我的处理函数使用css方法为 CSS borderbackground-color属性应用新值。这使得放置目标变亮,向用户表明可放置的元素与被拖动的元素有关系。我使用deactivate事件移除 CSS 属性值,并在用户释放鼠标按钮时将可放置的元素返回到其原始状态。(无论用户是否将可拖动的元素放到了可放下的元素上,只要拖动停止,就会触发此事件。)你可以在图 24-7 中看到效果。

9781430263883_Fig24-07.jpg

图 24-7 。使用激活和停用事件

处理重叠元素

您可以通过处理overout事件来改进您的拖放技术。当可拖动元素的 50%位于可放下元素的任何部分时,触发over事件。当元素不再重叠时,触发out事件。清单 24-9 显示了如何对这些事件做出响应。

清单 24-9 。使用超出和超出事件

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

        $("#droppable").droppable({
            drop: function() {
                $("#draggable").text("Dropped");
            },
            over: function() {
                $("#droppable").css({
                    border: "medium double green",
                    backgroundColor: "lightGreen"
                });
            },
            out: function() {
                $("#droppable").css("border", "").css("background-color", "");
            }
        });
    });
</script>
...

我使用了与前一个例子相同的事件处理函数,但是我将它们与overout事件相关联。当可拖放元素至少有 50%与可拖动元素重叠时,可拖放元素将显示边框和背景色,如图图 24-8 所示。

9781430263883_Fig24-08.jpg

图 24-8 。响应过度和过度事件

image 提示50%的限制被称为容差,您可以用不同的容差配置可删除的元素,正如我在“更改重叠容差”一节中演示的那样

配置可拖放的交互

我在这一部分打破了通常的模式,因为这些事件对可丢弃的交互是如此重要。当然,这种交互有许多设置可以用来改变它的行为方式,这些在表 24-7 中有描述。

表 24-7 。可删除的设置

环境 描述
disabled true时,交互最初被禁用。默认为false
accept 缩小可拖放元素将响应的可拖动元素。默认为*,匹配所有元素。
activeClass 指定将在响应activate事件时应用并在响应deactivate事件时移除的类。
hoverClass 指定将在响应over事件时应用并在响应out事件时移除的类。
tolerance 指定在触发on事件之前必须发生的重叠量。

image 提示在“调整拖放”一节中,我描述了一些改变可拖放元素之间关系的附加设置。

限制可接受的可拖动元素

您可以通过应用accept设置来限制您希望通过可拖放交互接收的元素集。通过提供一个选择器作为值来使用accept设置。当一个可拖动的元素与选择器匹配时,这样做的效果是只触发可放下的事件。清单 24-10 提供了一个例子。

清单 24-10 。限制可接受的元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .draggable, #droppable {font-size: large; border: thin solid black; padding: 4px;
            width: 100px; text-align: center; background-color: lightgray; margin: 4px;}
        #droppable {padding: 20px; position: absolute; left: 5px; bottom: 5px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $(".draggable").draggable();

            $("#droppable").droppable({
                drop: function(event, ui) {
                    ui.draggable.text("Dropped");
                },
                activate: function() {
                    $("#droppable").css({
                        border: "medium double green",
                        backgroundColor: "lightGreen"
                    });
                },
                deactivate : function() {
                    $("#droppable").css("border", "").css("background-color", "");
                },
                accept: "#drag1"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="droppable">
        Drop Here
    </div>
    <div id="drag1" class="draggable">
        Drag 1
    </div>
    <div id="drag2" class="draggable">
        Drag 2
    </div>
</body>
</html>

本例中有两个可拖动的元素,id 分别为drag1drag2。在创建可拖放元素时,我使用了accept设置来指定只接受drag1元素。当drag1元素被拖动时,你会看到与上一个例子相同的效果。activatedeactivateoveroutdrop事件都在适当的时刻触发。然而,当您拖动drag2元素时,它无法匹配我为 accept 设置指定的选择器,并且这些事件不会触发。我仍然可以拖动元素,但是我不能再把它放到可拖放的元素上。你可以在图 24-9 中看到效果。

9781430263883_Fig24-09.jpg

图 24-9 。使用接受设置

注意,我改变了选择拖放元素的方式,这样我就可以调用text方法。当文档中只有一个可拖动元素时,我只使用了id属性,如下所示:

...
$("#draggable").text("Dropped");
...

在这个例子中,有多个可拖动的元素,所以通过id选择不会起作用,因为我将总是改变同一个元素上的文本,而不管哪个元素被拖放。相反,我使用了ui对象,jQuery UI 将它作为事件处理函数的附加参数。ui对象的 draggable 属性返回一个 jQuery 对象,该对象包含用户正在拖动或已经放下的元素,允许我如下定位该元素:

...
ui.draggable.text("Dropped");
...

使用类突出显示可删除的

您可以使用activeClasshoverClass设置 来更改可拖放元素的外观,而无需使用activatedeactivateoverout事件。清单 24-11 提供了一个演示。

清单 24-11 。使用 activeClass 和 hoverClass 设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .draggable, #droppable {font-size: large; border: thin solid black; padding: 4px;
            width: 100px; text-align: center; background-color: lightgray; margin: 4px;}
        #droppable {padding: 20px; position: absolute; left: 5px; bottom: 5px}
        #droppable.active {border: thick solid green}
        #droppable.hover {background-color: lightgreen}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $(".draggable").draggable();

            $("#droppable").droppable({
                drop: function(event, ui) {
                    ui.draggable.text("Dropped");
                },
                activeClass: "active",
                hoverClass: "hover"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="droppable">
        Drop Here
    </div>
    <div class="draggable">
        Drag Me
    </div>
</body>
</html>

我定义了两个新的 CSS 样式,我在清单中突出显示了这两个样式。我创建了特定于 draggable 元素的id(例如#draggable.active)的类,这样它们比我一直使用的其他样式(例如#droppable)更加特定,并且它们具有优先权。参见第三章了解 CSS 样式应用于元素的详细规则。

定义了这些样式后,我将它们命名为activeClasshoverClass设置的值。可删除交互负责在可删除元素中添加和删除这些类,以响应事件。你可以在图 24-10 中看到结果。

9781430263883_Fig24-10.jpg

图 24-10 。使用 activeClass 和 hoverClass 设置

更改重叠公差

默认情况下,只有当至少 50%的可拖动元素与可放下元素重叠时,才会触发over事件。您可以使用tolerance设置 进行更改,该设置接受表 24-8 中显示的值。

表 24-8 。公差值

价值 描述
fit 被拖动的元素必须与可放下的元素完全重叠。
intersect 被拖动元素的至少 50%必须与可拖放元素重叠。这是默认设置。
pointer 无论用户从哪里抓取了可拖动元素,鼠标指针都必须在可拖动元素上。
touch 被拖动的元素必须与可放下的元素重叠任意多的距离。

我最常用的两个值是fittouch,因为它们代表了用户最容易理解的方法。当我保存被放下的项目的位置时,我使用fit,当我让被放下的项目恢复到原来的位置时,我使用touch(我将在本章后面演示)。清单 24-12 显示了fittouch设置的使用。

清单 24-12 。更改可拖动元素的公差

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .draggable, .droppable {font-size: large; border: thin solid black; padding: 4px;
            width: 100px; text-align: center; background-color: lightgray;}
        .droppable {margin-bottom: 10px; margin-right: 5px; height: 50px; width: 120px}
        #dropContainer {position: absolute; right: 5px;}
        div span {position: relative; top: 25%}
        .droppable.active {border: thick solid green}
        .droppable.hover {background-color: lightgreen}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $(".draggable").draggable();

            $("div.droppable").droppable({
                drop: function(event, ui) {
                    ui.draggable.text("Dropped");
                },
                activeClass: "active",
                hoverClass: "hover",
                tolerance: "fit"
            });

            $("#touchDrop").droppable("option", "tolerance", "touch");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dropContainer">
        <div id="fitDrop" class="droppable">
            <span>Fit</span>
        </div>
        <div id="touchDrop" class="droppable">
            <span>Touch</span>
        </div>
    </div>
    <div class="draggable">
        <span>Drag Me</span>
    </div>
</body>
</html>

在清单 24-12 中,我创建了两个可删除的元素,其中一个用公差设置的fit值配置,另一个用touch值配置。有一个单一的可拖动元素,图 24-11 显示了不同值产生的效果。我在over事件触发的那一刻拍摄了每一张截图。请注意,当tolerance设置用于确定重叠时,包含了我应用于可拖放元素的边界。

9781430263883_Fig24-11.jpg

图 24-11 。将“适合”和“接触”值用于公差设置

使用可丢弃的方法

可拖放的交互只定义了您看到的由小部件实现的核心方法集。没有特定于交互的方法。表 24-9 描述了那些可用的。

表 24-9 。可丢弃的方法

方法 描述
droppable("destroy") 从元素中移除交互
droppable("disable") 禁用了可拖放的交互
droppable("enable") 启用可拖放的交互
droppable("option") 更改一个或多个设置

调整拖放

您可以使用一些附加设置来微调 jQuery UI 拖放的工作方式。在本节中,我将描述这些设置并演示它们的用法。

使用元素范围

在本章的前面,我展示了如何使用 drop ableaccept设置来过滤激活拖放区的元素。使用选择器对于简单的项目来说很好,但是如果您有许多可拖动的元素需要管理,选择器可能会变得过于复杂和容易出错。

另一种方法是将scope设置应用到可拖动和可放下的元素上。可拖动元素将激活具有相同范围值的可拖放元素。清单 24-13 显示了正在使用的scope设置。

image 提示scope设置可以与accept设置在可删除元素中结合使用。只有当可拖动元素共享同一个scope并匹配由accept设置定义的选择器时,可拖动元素才会被激活。

清单 24-13 。使用范围设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .draggable, .droppable {font-size: large; border: medium solid black;
            padding: 4px; width: 100px; text-align: center;
            background-color: lightgray; margin-bottom: 10px;}
        .droppable {margin-right: 5px; height: 50px; width: 120px}
        #dropContainer {position: absolute; right: 5px;}
        div span {position: relative; top: 25%}
        .droppable.active {border: medium solid green}
        .droppable.hover {background-color: lightgreen}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("#apple").draggable({
                scope: "fruit"
            });
            $("#orchid").draggable({
                scope: "flower"
            });

            $("#flowerDrop").droppable({
                activeClass: "active",
                hoverClass: "hover",
                scope: "flower"
            });

            $("#fruitDrop").droppable({
                activeClass: "active",
                hoverClass: "hover",
                scope: "fruit"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dropContainer">
        <div id="flowerDrop" class="droppable">
            <span>Flowers</span>
        </div>
        <div id="fruitDrop" class="droppable">
            <span>Fruit</span>
        </div>
    </div>
    <div id="orchid" class="draggable">
        <span>Orchid</span>
    </div>
    <div id="apple" class="draggable">
        <span>Apple</span>
    </div>
</body>
</html>

在清单 24-13 中,我创建了两个可拖动的元素和两个可放下的元素。当创建这些元素时,我将它们分配给两个scope值之一:fruitflower。结果是每个可拖动元素都将被激活,并且只被具有相同范围的可拖放元素接受,如图图 24-12 所示。

9781430263883_Fig24-12.jpg

图 24-12 。按范围对可拖动和可放下的元素进行分组

image 注意注意,我在对draggabledroppable方法的初始调用中为每个元素定义了scope,而不是使用option方法。在我写这篇文章的时候,jQuery UI 中有一个 bug,在交互创建之后分配作用域不起作用。

使用辅助元素

helper设置 允许您指定一个将被拖动的元素来代替可拖动的元素,保留原来的可拖动元素。这是一个与前面的例子完全不同的效果,在前面的例子中,可拖动元素已经从它的原始位置移开。清单 24-14 展示了一个使用辅助元素的例子。

清单 24-14 。使用大的可拖动元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .draggable, .droppable {font-size: large; border: medium solid black;
            padding: 4px; width: 150px; text-align: center;
            background-color: lightgray; margin-bottom: 10px;}
        .droppable {margin-right: 5px; height: 50px; width: 120px}
        #dropContainer {position: absolute; right: 5px;}
        div span {position: relative; top: 25%}
        .droppable.active {border: medium solid green}
        .droppable.hover {background-color: lightgreen}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("div.draggable").draggable({
                helper: "clone"
            });

            $("#basket").droppable({
                activeClass: "active",
                hoverClass: "hover"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dropContainer">
        <div id="basket" class="droppable">
            <span>Basket</span>
        </div>
    </div>
    <div class="draggable">
        <img src="lily.png"/><label for="lily">Lily</label>
    </div>
</body>
</html>

值克隆告诉 jQuery UI 复制可拖动元素及其所有内容,并将结果用作辅助元素。你可以在图 24-13 中看到效果。当用户放下辅助元素时,它会被移除,将可拖动和可放下的元素留在原来的位置。

9781430263883_Fig24-13.jpg

图 24-13 。大的可拖动元素

如图所示,原来的可拖动元素保留在原来的位置,只有辅助元素随着用户的鼠标在屏幕上移动。像本例中这样的大型可拖动元素使得用户很难看到文档中的底层元素,包括可拖放元素的位置。您可以通过提供一个函数作为助手 设置的值来解决这个问题,如清单 24-15 所示。

清单 24-15 。使用助手设置

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

        $("div.draggable").draggable({
            helper: function() {
                return $("<img src=lily.png />")
            }
        });

        $("#basket").droppable({
            activeClass: "active",
            hoverClass: "hover"
        });
    });
</script>
...

当用户开始拖动元素时,jQuery UI 调用helper函数,并使用它返回的元素作为可拖动的项目。在本例中,我使用 jQuery 创建一个img元素。你可以在图 24-14 中看到效果。

9781430263883_Fig24-14.jpg

图 24-14 。使用助手

较小的图像充当可拖动元素的更紧凑的替身,使得在拖动时更容易看到文档的其余部分。

操作辅助元素

jQuery UI 传递给 droppable events 的ui对象包含一个helper属性,您可以使用该属性在拖动 helper 元素时对其进行操作。清单 24-16 展示了这个属性的使用,与overout事件相关联。

清单 24-16 。使用 ui.helper 属性

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

        $("div.draggable").draggable({
            helper: function() {
                return $("<img src=lily.png />")
            }
        });

        $("#basket").droppable({
            over: function(event, ui) {
                ui.helper.css("border", "thick solid green")
            },
            out: function(event, ui) {
                ui.helper.css("border", "")
            }
        });
    });
</script>
...

我使用overout事件以及ui.helper属性在辅助元素与可拖放元素重叠时显示一个边框。你可以在图 24-15 中看到结果。

9781430263883_Fig24-15.jpg

图 24-15 。操作辅助元素

对齐元素的边缘

您可以使用snap设置 使可拖动元素捕捉到它经过的元素的边缘。此设置的值是一个选择器。draggable 将与它附近与选择器匹配的任何元素的边缘对齐。清单 24-17 显示了snap设置的使用。

清单 24-17 。使用捕捉设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #snapper, .draggable, .droppable {font-size: large; border: medium solid black;
            padding: 4px; width: 150px; text-align: center;
            background-color: lightgray; margin-bottom: 10px;}
        .droppable {margin-right: 5px; height: 50px; width: 120px}
        #dropContainer {position: absolute; right: 5px;}
        div span {position: relative; top: 25%}
        .droppable.active {border: medium solid green}
        .droppable.hover {background-color: lightgreen}
        #snapper {position: absolute; left: 35%; border: medium solid black;
            width: 180px; height: 50px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("div.draggable").draggable({
                snap: "#snapper, .droppable",
                snapMode: "both",
                snapTolerance: 50
            });

            $("#basket").droppable({
                activeClass: "active",
                hoverClass: "hover"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="dropContainer">
        <div id="basket" class="droppable">
            <span>Basket</span>
        </div>
    </div>
    <div id="snapper"><span>Snap Here</span></div>
    <div class="draggable">
        <span>Drag Me</span>
    </div>
</body>
</html>

当可拖动元素靠近其中一个匹配元素时,它会跳跃(捕捉)以便两个最近的边接触。您可以选择任何要捕捉的元素,而不仅仅是可删除的元素。在清单 24-17 中,我添加了一个div元素并为snap设置定义了一个值,该值用于选择它和文档中可拖放的元素。使用屏幕截图来展示截图的效果几乎是不可能的,所以我鼓励您在浏览器中尝试这个例子。

有几个支持设置可用于调整捕捉行为。snapMode第一个是。通过此设置,您可以指定可拖动对象将吸附到哪些边缘。可接受的值有inner(对齐基础元素的内边缘)、outer(对齐外边缘)和both(对齐所有边缘,这是默认值)。

您使用snapTolerance设置来指定可拖动元素在捕捉到位置之前必须远离目标多远。默认为20,表示 20 像素。在清单 24-17 的中,我指定了一个50的值,这使得捕捉发生在更远的地方。为此设置获取正确的值非常重要。如果snapTolerance值太小,用户不会注意到对齐效果,如果值太大,可拖动元素开始意外地跳过屏幕,以对齐远处的元素

摘要

在本章中,我向您介绍了 jQuery UI 交互中最重要和最有用的两种:draggable 和 droppable。我向您展示了如何单独应用和配置这些交互,如何响应它们的事件,以及如何调整它们协同工作的方式,以获得对您向 web 应用用户提供的拖放体验的细粒度控制。在第二十五章中,我将向您展示其他 jQuery UI 交互。

二十五、使用其他交互

在这一章中,我将描述剩下的三个 jQuery UI 交互:可排序的可选择的可调整大小的。这些交互比draggabledroppable用得更少(也更没用),我在第二十四章中描述过。本章中的交互可能是有用的,但是它们使用的模型很难向用户突出显示。正因为如此,它们作为其他更传统的方法的补充表现得最好。表 25-1 提供了本章的总结。

表 25-1 。章节总结

问题 解决办法 列表
应用可排序交互 选择容器元素,并调用sortable方法 one
获取用户通过可排序交互创建的订单 调用toArrayserialize方法 2, 3
允许将元素从一个可排序项目拖动到另一个项目 使用connectWith设置 four
将可拖动元素与可排序项目连接 使用可拖动元素上的connectToSortable设置 five
指定哪些元素是可排序的 使用items设置 six
拖动可排序项目时创建的空白区域 使用placeholder设置 seven
忽略顺序的变化 使用cancel方法 eight
刷新可排序项目中的元素集 使用refresh方法 nine
获取有关正在进行的排序操作的信息 使用提供给事件处理函数的ui对象 Ten
应用可选交互 选择容器元素,并调用selectable方法 11, 12
防止元素被选中 使用cancel方法 Thirteen
应用可调整大小的交互 使用resizable方法 Fourteen
调整多个元素的大小 使用alsoResize设置 15, 16
限制可调整大小的元素的大小 使用maxHeightmaxWidthminHeightminWidth设置 Seventeen
为可调整大小的元素选择可拖动的边和角 使用handles设置 Eighteen

使用可排序交互

sortable交互允许用户通过拖动改变一组元素的顺序。通过选择包含想要排序的单个项目的元素,然后调用sortable方法,应用可排序交互,如清单 25-1 所示。

清单 25-1 。使用可排序交互

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable { width: 100px; background-color: lightgrey; font-size: large;
            float: left; margin: 4px; text-align: center; border: medium solid black;
            padding: 4px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#sortContainer").sortable();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="sortContainer">
        <div id="item1" class="sortable">Item 1</div>
        <div id="item2" class="sortable">Item 2</div>
        <div id="item3" class="sortable">Item 3</div>
    </div>
</body>
</html>

在清单 25-1 中,我创建了许多div元素,并将它们分配给sortable类。这个类对交互没有影响:我只是用它来设计元素的样式。为了创建交互,我选择父元素div(其idsortContainer)并调用sortable方法。结果是,我可以简单地通过将三个div元素拖动到一个新位置来改变它们的顺序。你可以在图 25-1 中看到效果(尽管,像本章中的所有例子一样,你将通过在浏览器中运行这个例子来更好地理解发生了什么)。

9781430263883_Fig25-01.jpg

图 25-1 。通过拖移来排序项目

为了演示sortable交互,我将标签为Item 2的元素拖到浏览器窗口的右侧。一旦我将元素拖过标签为Item 3的元素,jQuery UI 就会重新排列这些项目,使它们处于新的顺序。我只将一个元素拖动到一个位置,但是您可以一次将它们移动到几个位置。

获取可排序订单

在某些时候,您需要知道用户通过移动元素创建的顺序。要获得这些信息,您可以调用toArray方法,该方法返回排序元素的id属性值的 JavaScript 数组。清单 25-2 显示了在将当前订单写入控制台的例子中添加了一个按钮。

清单 25-2 。获取排序后的元素顺序

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

        $("<div id=buttonDiv><button>Get Order</button></div>").appendTo("body");
        $("button").button().click(function() {
            var order = $("#sortContainer").sortable("toArray");
            for (var i = 0; i < order.length; i++) {
                console.log("Position: " + i + " ID: " + order[i]);
            }
        });
    });
</script>
...

你可以在图 25-2 中看到效果。当按钮被按下时,我调用toArray方法并将结果数组的内容枚举到控制台。

9781430263883_Fig25-02.jpg

图 25-2 。添加一个按钮来写出排序顺序

对于图中的订单,按下按钮会产生以下输出:

Position: 0 ID: item2
Position: 1 ID: item3
Position: 2 ID: item1

您还可以使用serialize方法来生成一个字符串,该字符串很容易与表单一起使用。清单 25-3 提供了一个例子。

清单 25-3 。使用 serialize 方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable { width: 100px; background-color: lightgrey; font-size: large;
            float: left; margin: 4px; text-align: center; border: medium solid black;
            padding: 4px;}
            #buttonDiv {clear: both}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#sortContainer").sortable();

            $("<div id=buttonDiv><button>Get Order</button></div>").appendTo("body");
            $("button").button().click(function() {
                var formstring = $("#sortContainer").sortable("serialize");
                console.log(formstring);
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="sortContainer">
        <div id="item_1" class="sortable">Item 1</div>
        <div id="item_2" class="sortable">Item 2</div>
        <div id="item_3" class="sortable">Item 3</div>
    </div>
</body>
</html>

注意,我必须更改可排序元素的id值。serialize方法在生成其字符串时寻找<key>_<index>的模式。图 25-2 中所示命令的输出如下:

item[]=2&item[]=3&item[]=1

配置 可排序的交互

可排序交互依赖于我在第二十四章中描述的可拖动交互。这意味着我为该交互描述的选项(比如axistolerance)可以以同样的效果应用于配置可排序的交互。那些设置我就不再描述了。相反,表 25-2 显示了可分类交互特有且最有用的设置。我将在接下来的章节中描述这些设置。

表 25-2 。可排序设置

环境 描述
connectWith 指定要连接的另一个可排序元素,以便可以在它们之间拖动项目。默认为false,表示没有连接。
dropOnEmpty false时,物品不能放在不包含物品的已连接的可排序交互上。默认为true
items 指定可通过选择器排序的项目。缺省值是> *,它选择调用了sortable方法的元素的任何后代。
placeholder 指定一个类,该类将被分配给在可排序项目被拖动到新位置时为保留空间而创建的元素。

连接可排序的交互

我最喜欢的可排序特性是连接两个可排序交互的能力,允许在它们之间拖动项目。您可以使用connectWith设置来实现这一点,指定一个选择器来匹配您想要连接的元素。您可以通过在两个可排序元素上使用connectWith设置来创建一个双向连接,如清单 25-4 中的所示。

清单 25-4 。连接可排序的交互

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable { width: 100px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
        #fruitContainer {position: absolute; right:50px}
        #flowerContainer {position: absolute; left:50px}
        div.flower {background-color: lightgreen}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#fruitContainer").sortable({
                connectWith: "#flowerContainer"
            });
            $("#flowerContainer").sortable({
                connectWith: "#fruitContainer"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="fruitContainer" class="sortContainer">
        <div id="fruit_1" class="sortable fruit">Apple</div>
        <div id="fruit_2" class="sortable fruit">Orange</div>
        <div id="fruit_3" class="sortable fruit">Banana</div>
        <div id="fruit_4" class="sortable fruit">Pear</div>
    </div>
    <div id="flowerContainer" class="sortContainer">
        <div id="flower_1" class="sortable flower">Aster</div>
        <div id="flower_2" class="sortable flower">Peony</div>
        <div id="flower_3" class="sortable flower">Lily</div>
        <div id="flower_4" class="sortable flower">Orchid</div>
    </div>
</body>
</html>

在清单 25-4 中,我创建了两组项目,并在它们的容器元素上调用了sortable方法。我使用了connectwith设置来关联每个可排序的条目,并且图 25-3 显示了结果。

9781430263883_Fig25-03.jpg

图 25-3 。在连接的可排序交互之间拖动元素

将可拖动元素与可排序元素连接起来

你也可以将一个可拖动的元素和一个可排序的元素连接起来。通过在可拖动元素上应用connectToSortable设置,指定一个与您想要连接的可拖动元素相匹配的选择器,可以做到这一点。清单 25-5 展示了这是如何做到的。

清单 25-5 。连接可拖动元素和可排序元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable { width: 100px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
        #fruitContainer {position: absolute; right:50px}
        #flowerContainer {position: absolute; left:50px}
        div.flower {background-color: lightgreen}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#fruit_1").draggable({
                connectToSortable: "#flowerContainer",
                helper: "clone"
            });
            $("#flowerContainer").sortable();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="fruitContainer" class="sortContainer">
        <div id="fruit_1" class="sortable fruit">Apple</div>
    </div>
    <div id="flowerContainer" class="sortContainer">
        <div id="flower_1" class="sortable flower">Aster</div>
        <div id="flower_2" class="sortable flower">Peony</div>
        <div id="flower_3" class="sortable flower">Lily</div>
        <div id="flower_4" class="sortable flower">Orchid</div>
    </div>
</body>
</html>

在清单 25-5 中,我将水果项目的数量减少到一个,并使其可拖动,连接到可排序的鲜花列表。结果是可拖动的项目可以被添加到可排序列表中,如图图 25-4 所示。当可拖动项目的helper设置为clone时,此设置效果最佳。它对其他值也有效,但是会报告一个错误。

9781430263883_Fig25-04.jpg

图 25-4 。连接可排序项目和可拖动项目

选择可排序的项目

您可以选择容器中的哪些物品是可分类的。通过items设置可以做到这一点,它的值是一个选择器,匹配您想要启用排序的元素。无法重新排列与选择器不匹配的元素。清单 25-6 演示了。

清单 25-6 。选择可排序的特定元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable { width: 100px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
        #fruitContainer {position: absolute; right:50px}
        #flowerContainer {position: absolute; left:50px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("div.flower:even").css("background-color", "lightgreen")
            $("#flowerContainer").sortable({
                items: ".flower:even"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="flowerContainer" class="sortContainer">
        <div id="flower_1" class="sortable flower">Aster</div>
        <div id="flower_2" class="sortable flower">Peony</div>
        <div id="flower_3" class="sortable flower">Lily</div>
        <div id="flower_4" class="sortable flower">Orchid</div>
    </div>
</body>
</html>

在清单 25-6 中,我使用了items设置来指定容器中只有偶数编号的元素是可排序的。在图 25-5 中,可以对AsterLily元素进行排序,但是PeonyOrchid元素不会对被拖动作出反应并保持在原位。

9781430263883_Fig25-05.jpg

图 25-5 。选择可以排序的项目

在使用items设置时,有一个奇怪的地方你应该注意,我已经在图的最后一帧中展示了。与选择器不匹配的元素不能被拖动到新位置,除非它已被另一个元素移走。所以在图中,我将Aster元素拖到一个新的位置,这迫使Peony元素移动。一旦被移动,Peony元素将响应被拖动和排序,就好像它匹配items选择器一样。

设计空白的空间

当您将项目拖到新位置时,它留下的空间保持空白。你可以通过placeholder设置将一个 CSS 类应用到这个空间。这是强调空白空间是拖放目标的一种有用方式。清单 25-7 显示了placeholder设置的使用。

清单 25-7 。使用占位符设置

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable {width: 100px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
        #flowerContainer {position: absolute; left:25%}
        .emptySpace {border: medium dotted red; height: 25px; margin: 4px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#flowerContainer").sortable({
                placeholder: "emptySpace"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="flowerContainer" class="sortContainer">
        <div id="flower_1" class="sortable ">Aster</div>
        <div id="flower_2" class="sortable ">Peony</div>
        <div id="flower_3" class="sortable">Lily</div>
        <div id="flower_4" class="sortable">Orchid</div>
    </div>
</body>
</html>

在清单 25-7 的中,我定义了一个名为emptySpace的 CSS 类,它定义了heightmargin属性的大小,并定义了一个红色圆点border。我使用placeholder设置来指定这个类,如图 25-6 所示,当我拖动一个元素来排序它时,它留下的空间被分配给emptySpace类。

9781430263883_Fig25-06.jpg

图 25-6 。使用占位符设置

使用可排序的方法

可排序交互定义了所有标准的 jQuery UI 方法,以及一些特定于使用可排序元素的方法。表 25-3 描述了这些方法。

表 25-3 。可排序的方法

方法 描述
sortable("destroy") 从元素中移除交互
sortable("disable") 禁用了可排序交互
sortable("enable") 启用可排序交互
sortable("option") 更改一个或多个设置
sortable("toArray") 返回一个数组,其中包含一组已排序的id属性值(参见“获取可排序顺序”一节中的示例)
sortable("refresh") 刷新了可排序的交互
sortable("cancel") 取消排序操作

取消排序

你可以使用cancel方法来防止元素被排序。这是应该谨慎进行的事情,因为它实际上忽略了用户已经采取的行动。如果你取消了一个排序,你应该确保用户知道为什么会这样。清单 25-8 提供了一个结合使用cancel方法和update事件的例子。当用户拖动一个元素创建一个新的排序顺序后释放鼠标按钮时,触发update事件。我在“使用可排序事件”一节中描述了可排序事件

清单 25-8 。使用取消方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable {width: 100px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#error").dialog({autoOpen: false, modal: true})

            $("#flowerContainer").sortable({
                update: function() {
                    var sortedItems = $("#flowerContainer").sortable("toArray");
                    if (sortedItems[0] != "item_1") {
                        $("#error").dialog("open")
                        $("#flowerContainer").sortable("cancel")
                    }
                }
            });
        });
    </script>
</head>
<body>
    <div id="error">The King must be first</div>
    <h1>Jacqui's Flower Shop</h1>
    <div id="flowerContainer" class="sortContainer">
        <div id="item_1" class="sortable ">King</div>
        <div id="item_2" class="sortable ">Queen</div>
        <div id="item_3" class="sortable ">Jack</div>
        <div id="item_4" class="sortable">10</div>
    </div>
</body>
</html>

在清单 25-8 中,如果用户创建的新排序意味着King元素不在列表的第一个位置,我就调用cancel方法。我使用第二十二章中描述的对话框部件来提醒用户这个问题。允许影响其他可排序元素的更改继续进行。

刷新可排序元素

refresh方法使 sortable 交互刷新 sortable 容器中元素的缓存。清单 25-9 展示了如何使用这个特性来添加新的可排序元素。

清单 25-9 。添加新的可排序元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable {width: 100px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#flowerContainer").sortable();

            var itemCount = 2;

            $("button").click(function() {
                $("<div id=flower_" + (itemCount++) + " class=sortable>Item " +
                  itemCount + "</div>").appendTo("#flowerContainer");
                $("#flowerContainer").sortable("refresh");
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <button>Add Sortable Item</button>
    <div id="flowerContainer" class="sortContainer">
        <div id="flower_1" class="sortable">Aster</div>
        <div id="flower_2" class="sortable">Peony</div>
    </div>
</body>
</html>

在清单 25-9 中,我在文档中添加了一个button,向可排序的容器中添加新的条目,并调用refresh方法来确保这些条目可以正确排序。

使用可排序事件

可排序交互支持可拖动交互定义的所有事件,我在第二十四章中描述过。表 25-4 描述了可排序交互特有的事件。

表 25-4 。可排序事件

事件 描述
change 当用户对元素进行排序时位置发生变化时触发
receive 当一个项目从一个连接的项目被拖动到这个可排序的项目时触发
remove 当一个项目从这个可排序项目拖到一个连接的项目时触发
sort 排序过程中每次鼠标移动时触发
update 当用户停止拖动某项并且该项的顺序已经改变时触发

当触发这些事件时,jQuery UI 通过一个ui对象参数提供附加信息,该参数的属性如表 25-5 所示。

表 25-5 。可排序的 ui 对象属性

财产 描述
helper 返回辅助元素
position 以具有topleft属性的对象的形式返回辅助对象的当前位置
item 返回包含当前拖动项的 jQuery 对象
placeholder 返回包含占位符元素的 jQuery 对象
sender 返回一个 jQuery 对象,该对象包含元素所源自的连接的可排序表(当没有连接的可排序表时,该属性为 null)

清单 25-10 展示了ui对象与sortchange事件的使用。

清单 25-10 。使用更改和排序事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.sortable {width: 100px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
        #flowerContainer {position: absolute; left:10px}
        #info {position: absolute; right: 10px; border: medium solid black; padding: 4px}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#flowerContainer").sortable({
                sort: function(event, ui) {
                    $("#itemId").text(ui.item.attr("id"))
                },
                change: function(event, ui) {
                    $("#pos").text($("#flowerContainer *").index(ui.placeholder))
                }
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="flowerContainer" class="sortContainer">
        <div id="flower_1" class="sortable ">Aster</div>
        <div id="flower_2" class="sortable ">Peony</div>
        <div id="flower_3" class="sortable">Lily</div>
        <div id="flower_4" class="sortable">Orchid</div>
    </div>
    <div id="info" class="ui-widget">
        <div>Item ID: <span id="itemId">None</span></div>
        <div>Pos: <span id="pos">None</span></div>
    </div>
</body>
</html>

我使用事件来显示关于排序操作的信息。对于sort事件,我读取了ui.item属性并获得了被拖动元素的id属性。对于 change 事件,我使用了ui.placeholder属性,并使用了index方法来确定它在可排序元素中的位置。

使用可选交互

可选择的交互允许用户通过拖动鼠标或点击单个元素来选择一个或多个元素。您通过selectable方法应用交互,如清单 25-11 所示。

清单 25-11 。应用可选交互

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        div.flower {width: 200px; background-color: lightgrey; font-size: large;
            margin: 4px; text-align: center; border: medium solid black; padding: 4px;}
        #flowerContainer {position: absolute; left:10px}
        div.ui-selected {border: medium solid green; background-color: lightgreen}
        div.ui-selecting {border: medium solid green}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#flowerContainer").selectable();
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="flowerContainer">
        <div id="flower_1" class="flower">Aster</div>
        <div id="flower_2" class="flower">Peony</div>
        <div id="flower_3" class="flower">Lily</div>
        <div id="flower_4" class="flower">Orchid</div>
    </div>
</body>
</html>

您将可选交互应用于包含您希望用户能够选择的元素的元素。在这种情况下,我使用了本章前面用于可排序交互的相同的div元素。我选择容器并调用selectable方法,如下所示:

...
$("#flowerContainer").selectable();
...

尽管我现在已经将可选交互应用到了我的容器中,但是我还需要为特定的类定义一对 CSS 样式来给用户提供视觉反馈。以下是我与这些类关联的样式:

...
div.ui-selected {border: medium solid green; background-color: lightgreen}
div.ui-selecting {border: medium solid green}
...

可选交互将这些类应用到我的元素,以反映它们的选择状态。当用户拖动鼠标选择特定区域中的元素时,应用ui.selecting类,当元素被选中时,应用ui-selected类(因为用户点击了该元素,或者因为它位于鼠标拖动覆盖的区域中)。我使用了简单的样式,只使用绿色的边框和背景。在图 25-7 中可以看到拖动鼠标选择元素的效果。

9781430263883_Fig25-07.jpg

图 25-7 。用鼠标选择元素

用户必须开始在容器元素内拖动鼠标来开始选择过程。您可以在图的中间框架中看到被选中区域的轮廓(称为选框)——此时,jQuery UI 已经应用了ui-selecting类。当释放鼠标时,选取框重叠的元素被选中,并应用ui-selected类,如图的最后一帧所示。

用户也可以通过点击来选择元素。可以选择多个元素,按住 Control/Meta 键可以进行不连续的选择。如果您发现单击会导致单个选定元素切换,您需要添加清单 25-12 中所示的内容。

清单 25-12 。为可选择的交互启用多个元素选择

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#flowerContainer")
            .bind("mousedown", function(e) {e.metaKey = true;})
            .selectable();
    });
</script>
...

当用户按下鼠标按钮时,应用ui-selecting类。当释放鼠标按钮时,应用ui-selected类。

配置可选交互

您可以使用表 25-6 中描述的设置来配置可选交互。

表 25-6 。可选设置

环境 描述
disabled true时,交互最初被禁用。默认为false
autoRefresh true时,交互在每个选择操作开始时刷新每个可选元素的大小和位置。默认为true
cancel 阻止匹配元素被选中的选择器字符串。
delay 参见第二十四章中可拖动交互的delay设置。
distance 参见第二十四章中关于可拖动交互的distance设置。
filter 用于匹配容器中可选元素的选择器。默认为*,匹配所有元素。

这些设置中的大多数是不言而喻的,或者与其他交互相同。不过,特别有趣的是cancel设置,您可以用它来使元素不被用户选择。清单 25-13 演示了。

清单 25-13 。使用取消设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#flowerContainer")
            .bind("mousedown", function(e) {e.metaKey = true;})
            .selectable({
                cancel: "#flower_3"
            });
    });
</script>
...

在这个脚本中,我使用了一个选择器来防止 ID 为flower_3的元素被选中。当用户通过点击元素来选择元素时,这种方法很有效,但是不能阻止通过拖动来选择。因此,小心使用cancel设置。

使用可选的交互方法

可选交互仅定义一个唯一的方法,如表 25-7 所述。其他方法是所有小部件和交互通用的方法。

表 25-7 。可选方法

方法 描述
selectable("destroy") 从元素中移除交互
selectable("disable") 禁用可选交互
selectable("enable") 启用可选交互
selectable("option") 更改一个或多个设置
selectable("refresh") 刷新可选交互;这是使用false作为autoRefresh设置值的手动替代方法

使用可选的交互事件

可选交互定义了表 25-8 中显示的事件。

表 25-8 。可选方法

事件 描述
create 当交互应用于元素时触发。
selected 当一个项目被选中时触发。如果选择了多个项目,将为每个项目触发一次该事件。
selecting 当用户开始选择过程(通过按下鼠标按钮或拖动鼠标)时触发。
unselected 当取消选择项目时触发。如果未选择多个项目,则每个项目都会触发一次该事件。
unselecting 当用户通过按下鼠标按钮开始取消选择过程时触发。

jQuery UI 通过一个ui对象为大多数事件提供了附加信息。对于selectedselecting事件,ui对象有一个名为selected的属性,该属性包含与已经(或即将)被选中的元素相对应的HTMLElement。对于unselectedunselecting事件,ui对象有一个执行相同目的的unselected属性。

使用可调整大小的交互

可调整大小的交互将拖动手柄添加到允许用户调整大小的元素。有些浏览器对文本区域自动执行此操作,但是可调整大小的交互让我们可以将此功能应用于文档中的任何元素。清单 25-14 展示了使用resizable方法执行的可调整大小的交互的应用。

清单 25-14 。应用可调整大小的交互

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #aster, #lily {text-align: center; width: 150px; border: thin solid black;
            padding: 5px; float: left; margin: 20px}
        #aster img, #lily img {display: block; margin: auto}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("#aster").resizable({
                alsoResize: "#aster img"
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <div id="aster" class="ui-widget">
        <img src="aster.png" />
        Aster
    </div>
    <div id="lily" class="ui-widget">
        <img src="lily.png" />
        Lilly
    </div>
</body>
</html>

在清单 25-14 中,我创建了两个div元素,它们的内容是一个img和一些文本。我在script 中选择其中一个并应用resizable方法(使用alsoResize设置,我将在本章稍后描述)。jQuery UI 为选中的元素添加了一个拖动手柄,允许我在垂直和水平方向调整它的大小,如图 25-8 所示。在图中,我增加了元素的高度,减少了宽度。

9781430263883_Fig25-08.jpg

图 25-8 。使用拖动手柄更改可调整大小的元素的尺寸

配置可调整大小的交互

您可以使用表 25-9 中描述的设置来配置可调整大小的交互。可调整大小的交互依赖于我在第二十四章中描述的可拖动交互。这意味着,除了表中描述的设置,您还可以使用可拖动设置来配置可调整大小的交互,包括delaydistancegridcontainment

表 25-9 。可调整大小的设置

环境 描述
alsoResize 用于匹配应与可调整大小的元素同时调整大小的元素的选择器。缺省值是false,意味着不调整其他元素的大小。
aspectRatio true时,元素的纵横比在调整大小时保持不变。默认为false
autoHide true时,只有当鼠标悬停在可调整大小的元素上时,拖动手柄才可见。默认为false
ghost true时,会绘制一个半透明的帮助元素,向用户显示该元素的新大小。默认为true
handles 指定拖动手柄在可调整大小的元素上的放置位置。有关支持值的列表,请参阅本章后面的内容。
maxHeight 指定元素可以调整到的最大高度。默认为null,表示无限制。
maxWidth 指定元素可以调整到的最大宽度。默认为null,表示无限制。
minHeight 指定元素可以调整到的最小高度。默认值为 10 像素。
minWidth 指定元素可以调整到的最小宽度。默认值为 10 像素。

调整相关元素的大小

在我看来,alsoResize是配置可调整大小的交互最有用的设置。它允许您指定额外的元素,这些元素将随您应用了resizable方法的元素一起调整大小。我使用这个主要是为了确保内容元素的大小与其父元素同步,正如我在本章前面所演示的,当时我选择了用div调整大小的img元素。首先,它有助于理解当你有内容元素而不使用alsoResize设置时会发生什么。清单 25-15 设置场景。

清单 25-15 。在没有 alsoResize 设置的情况下使用内容调整元素的大小

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#aster").resizable();
    });
</script>
...

如果没有alsoResize设置,只有div元素会改变大小。内容元素将保持原样。你可以在图 25-9 中看到发生了什么。

9781430263883_Fig25-09.jpg

图 25-9 。调整元素的大小,但不调整其内容

有时候这很有用,但是我发现自己几乎每次使用可调整大小的交互时都在使用alsoResize设置。对我来说,alsoResize设置的好处是匹配的元素不局限于你要调整大小的元素的内容。您可以指定任何元素,如清单 25-16 所示。

清单 25-16 。使用 alsoResize 设置调整附加元素的大小

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#aster").resizable({
            alsoResize: "#aster img, #lily, #lily img"
        });
    });
</script>
...

在这个脚本中,我扩大了选择范围,以包括文档中的其他divimg元素。这意味着当我调整可调整大小的div元素时,jQuery UI 会同时调整四个元素的大小。你可以在图 25-10 中看到效果。

9781430263883_Fig25-10.jpg

图 25-10 。调整多个元素的大小

约束可调整大小的元素大小

您可以通过应用maxHeightmaxWidthminHeightminWidth设置来限制可调整大小的元素的大小。所有四个设置的值都是像素数或null,这意味着没有限制。清单 25-17 显示了如何使用这些设置。

image 提示minWidthminHeight设置的默认值为 10 像素。如果该值更小,jQuery UI 将无法显示拖动手柄,这意味着用户将无法再次增加尺寸。小心使用较小的值。

清单 25-17 。限制可调整大小的元素的大小

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#aster").resizable({
            alsoResize: "#aster img",
            maxWidth: 200,
            maxHeight: 150
        });
    });
</script>
...

image 提示你也可以使用由可拖动交互定义的包容设置,我在第二十四章中描述过。这允许您将可调整大小的元素的最大大小限制为另一个元素的大小。

定位拖动控制柄

您可以通过handles设置来指定哪些棱角可以被拖动。该设置的值可以是all(意味着所有的边和角都是可拖动的)或罗盘点的组合(neswnesenwsw),以指定各个角和边。

您可以指定多个值,用逗号分隔。该设置的默认值是e, s, se,这意味着右下角(se)和右边(e)和下边(s)将是可拖动的。jQuery UI 仅在右下角绘制一个对角拖动手柄,并且只有当您将se指定为handles值的一部分时。对于所有其他的边和角,当鼠标悬停在边或角上时,光标将改变以指示可以拖动。清单 25-18 显示了handles设置的使用。

清单 25-18 。使用手柄设置

...
<script type="text/javascript">
    $(document).ready(function() {
        $("#aster").resizable({
            alsoResize: "#aster img"
        });

        $("#lily").resizable({
            alsoResize: "#lilyimg",
            handles: "n, s, e, w"

        });
    });
</script>
...

在这个脚本中,我调整了两个div元素的大小,并对其中一个应用了一组自定义的拖动手柄。你可以在图 25-11 中看到 jQuery UI 是如何处理可见的拖动手柄和光标变化的。

9781430263883_Fig25-11.jpg

图 25-11 。使用手柄设置

摘要

在本章中,我解释并演示了三种 jQuery UI 交互:可排序、可选择和可调整大小。与我在第二十四章中描述的可拖动和可放下交互相比,这些不太常用,但如果小心应用,它们仍然很有用。与所有的交互一样,主要的挑战是让用户意识到,当 web 应用中没有标准化的视觉提示时,他或她可以拖动、选择、排序或调整元素的大小。因此,交互应该作为与应用或文档交互的其他机制的补充。这允许高级用户发现交互的优点,而其他用户依赖更明显和常规的技术。

二十六、重构示例:第三部分

在本书的这一部分,我向您介绍了 jQuery UI 小部件和交互。这些允许您创建丰富的 web 应用,这些应用具有一致的主题,并且可以根据您的需要不断地配置和调整。在这一章中,我将把其中的一些特性添加到示例中,以演示它们是如何结合在一起的。

查看重构的示例

当您最后一次重构示例时,您正处于重新创建一些使用核心 jQuery 库的 jQuery UI 功能的边缘。你可以在图 26-1 中看到我到了哪里。

9781430263883_Fig26-01.jpg

图 26-1 。之前重构的示例文档

本书前一部分中添加的内容包括数据模板、表单验证和 Ajax,但是我还添加了一个简单的产品轮播,在一行中显示可用的产品。在这一章中,我将使用其中的一些特性,但是我的重点将是应用 jQuery UI 。清单 26-1 显示了本章的起点。

清单 26-1 。本章的起始文档

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <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}}" value="0" />
        </div>
        {{/flowers}}
    </script>
    <script type="text/javascript">
        $(document).ready(function () {
            $.getJSON("mydata.json", function (data) {
                $("#flowerTmpl").template({ flowers: data })
                    .filter("*").appendTo("#products");
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="products"></div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

我使用getJSON方法从 JSON 文件中获取产品的细节,并使用数据模板生成元素。我将产品元素添加到一个单独的div元素中,该元素的idproducts。你可以在图 26-2 中看到结果。

9781430263883_Fig26-02.jpg

图 26-2 。本章的起始文档

展示产品

我将使用一个手风琴向用户展示产品。我只需要处理六个产品,但是我将把它们分成两个一组,并使用 jQuery 来创建 accordion 所需的元素结构。清单 26-2 显示了对文档的修改。

清单 26-2 。分类和构建花卉元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        .dcell img {height: 60px}
    </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 () {
            $.getJSON("mydata.json", function (data) {
                var flowers = $("#flowerTmpl").template({ flowers: data }).filter("*");

                var rowCount = 1;
                for (var i = 0; i < flowers.length; i += 2) {

                    $("<a>").text(data[i].name + "&" + data[i + 1].name)
                        .appendTo("<h2>").parent().appendTo("#products");

                    $("<div>").attr("id", "row" + (rowCount++))
                        .appendTo("#products")
                        .append(flowers.slice(i, i + 2))
                }
                $("#products").accordion();
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="products"></div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

我已经重写了传递给getJSON方法的函数来创建 accordion,包括构造元素结构和调用accordion方法。新的实现使用 JSON 数据对象来提取部分标题的花的名称,但仍然使用数据模板来生成 HTML 元素,这些元素被分割并放入包装器div元素中以适应 accordion 小部件。你可以在图 26-3 中看到添加对accordion方法的调用前后文档是如何出现的。

9781430263883_Fig26-03.jpg

图 26-3 。创建元素结构并调用 accordion 方法

添加购物篮

下一步是添加一个简单的购物篮,向用户显示她所做的选择。清单 26-3 显示了对示例文档的补充。

清单 26-3 。添加购物篮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>

    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        .dcell img {height: 60px}
        #basketTable {border: thin solid black; border-collapse: collapse}
        th, td {padding: 4px; width: 50px}
        td:first-child, th:first-child {width: 150px}
        #placeholder {text-align: center}
        #productWrapper {float: left; width: 65%}
        #basket {width: 30%; text-align: left; float: left; margin-left: 10px}
        #buttonDiv {clear: both}
    </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="rowTmpl" type="text/x-handlebars-template">
        <tr id="{{name}}"><td>{{product}}</td><td>{{val}}</td>
            <td><a href="#">Remove</a></td>
        </tr>
    </script>
    <script type="text/javascript">
        $(document).ready(function () {

            $.getJSON("mydata.json", function (data) {

                var flowers = $("#flowerTmpl").template({ flowers: data }).filter("*");

                var rowCount = 1;
                for (var i = 0; i < flowers.length; i += 2) {
                    $("<a>").text(data[i].name + " & " + data[i + 1].name)
                        .appendTo("<h2>").parent().appendTo("#products");
                    $("<div>").attr("id", "row" + (rowCount++))
                        .appendTo("#products")
                        .append(flowers.slice(i, i + 2));
                }
                $("#products").accordion();

                $("input").change(function (event) {
                    $("#placeholder").hide();
                    var fname = $(this).attr("name");
                    var row = $("tr[id=" + fname + "]");

                    if (row.length == 0) {
                        $("#rowTmpl").template({
                            name: fname,
                            val: $(this).val(),
                            product: $(this).siblings("label").text()
                        }).appendTo("#basketTable").find("a").click(function () {
                            removeTableRow($(this).closest("tr"));
                            var iElem = $("#products").find("input[name=" + fname + "]");
                            $("#products").accordion("option", "active",
                                iElem.closest("div[id^=row]").index("div[id^=row]"));
                            iElem.val(0).select();
                        });
                    } else if ($(this).val() != "0") {
                        row.children().eq(1).text($(this).val());
                    } else {
                        removeTableRow(row);
                    }
                });
            });

            function removeTableRow(row) {
                row.remove();
                if ($("#basketTable tbody").children(":visible").length == 1) {
                    $("#placeholder").show();
                }
            }
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="productWrapper">
            <div id="products"></div>
        </div>
        <div id="basket" class="ui-widget">
            <table border=1 id="basketTable">
                <tr><th>Product</th><th>Quantity</th><th>Remove</th></tr>
                <tr id="placeholder"><td colspan=3>No Products</td></tr>
            </table>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

折叠手风琴

我想把篮子放在手风琴旁边展示。为此,我将为accordion方法选择的元素包装在另一个div元素中,如下所示:

...
<div id="productWrapper">
    <div id="products"></div>
</div>
...

如果 accordion 小部件没有被设置为占据父元素宽度的 100 %,它就会变得混乱,所以我添加了 wrapper 元素,然后使用 CSS width属性来固定它的大小,如下所示:

...
#productWrapper {float: left;width: 65%}
...

accordion 小部件愉快地占据了包装器div元素的 100 %,而后者只占据了其父元素的 65%。

添加表格

我决定使用一个table元素来显示购物篮,我已经将它添加到了文档的静态元素中,如下所示:

...
<div id="basket" class="ui-widget">
    <table border=1 id="basketTable">
        <tr><th>Product</th><th>Quantity</th><th>Remove</th></tr>
        <tr id="placeholder"><td colspan=3>No Products</td></tr>
    </table>
</div>
...

就像手风琴一样,我将table元素放在一个包装器中,包装器的宽度是用 CSS 设置的:

...
#basket {width: 30%; text-align: left; float: left; margin-left: 10px}
...

table元素包含一个标题行和一个跨越整个表格的占位符。你可以在图 26-4 中看到创建的效果。

9781430263883_Fig26-04.jpg

图 26-4 。将表格添加到文档中

处理输入值更改

为了将表格链接到手风琴,我监听在getJSON函数中创建的input元素上的change事件,如下所示:

...
$("input").change(function (event) {
    $("#placeholder").hide();
    var fname = $(this).attr("name");
    var row = $("tr[id=" + fname + "]");

    if (row.length == 0) {
        $("#rowTmpl").template({
            name: fname,
            val: $(this).val(),
            product: $(this).siblings("label").text()
        }).appendTo("#basketTable").find("a").click(function () {
            removeTableRow($(this).closest("tr"));
            var iElem = $("#products").find("input[name=" + fname + "]");
            $("#products").accordion("option", "active",
                iElem.closest("div[id^=row]").index("div[id^=row]"));
            iElem.val(0).select();
        });
    } else if ($(this).val() != "0") {
        row.children().eq(1).text($(this).val());
    } else {
        removeTableRow(row);
    }
});
...

这个函数中发生了很多事情。当用户更改一个值时,我会检查表中是否已经有相应产品的一行。如果没有,那么我使用下面的模板创建一个新行:

...
<script id="rowTmpl" type="text/x-handlebars-template">
    <tr id="{{name}}"><td>{{product}}</td><td>{{val}}</td>
        <td><a href=#>Remove</a></td>
    </tr>
</script>
...

为了获取这个模板的值,我使用核心 jQuery 方法从触发事件的input元素中获取信息。我还想要产品的显示名称,这是通过导航 DOM 找到附近的label元素并读取其内容获得的,如下所示:

...
$(this).siblings("label").text()
...

我将新行追加到表中。占位符行已经隐藏,回到函数的开头:

...
$("#placeholder").hide();
...

您可以在图 26-5 中看到新增的行是如何出现的。用户在一个input元素中输入一个值,当焦点改变时,一个新的行出现在 basket 表中。

9781430263883_Fig26-05.jpg

图 26-5 。向购物篮表添加行

删除行

您可以看到,我已经将一个a元素添加到表格行中,作为数据模板的一部分。当我从数据模板创建行时,我为这个元素注册了一个处理程序,如下所示:

...
}).appendTo("#basketTable").find("a").click(function () {
    removeTableRow($(this).closest("tr"));
    var iElem = $("#products").find("input[name=" + fname + "]");
    $("#products").accordion("option", "active",
        iElem.closest("div[id^=row]").index("div[id^=row]"));
    iElem.val(0).select();
});
...

我做的第一件事是调用removeTableRow函数,将最近的祖先tr元素传递给a元素。removeTableRow函数使用remove方法从文档中删除指定的元素。如果没有与产品相关的行,它还会恢复表中的占位符行,如下所示:

...
function removeTableRow(row) {
    row.remove();
    if ($("#basketTable tbody").children(":visible").length == 1) {
        $("#placeholder").show();
    }
}
...

一旦删除了行,我就找到与产品中的行相关联的input元素。然后,我在 DOM 中导航,找到作为input元素的父元素的 accordion panel 元素,获取其对等元素中的index,并将其设置为 accordion 小部件的active选项。这具有打开包含用户刚刚从购物篮中删除的产品的折叠部分的效果。最后,我将input元素的值设置为零,并调用select方法,这样它就被聚焦,并且该值被选中。你可以在图 26-6 中看到效果(虽然这是你真的需要在浏览器中看到才能欣赏的东西)。

9781430263883_Fig26-06.jpg

图 26-6 。当表格行被删除时,聚焦于 accordion 中的输入元素

image 提示当用户在表格中有一行的input元素中输入零值时,我也会删除行。我使用removeTableRow函数来完成这个操作,以便在需要时显示占位符。

更新现有行

如果产品已经有一行,那么用户实际上是在改变她想要订购的数量。我没有删除和替换该行,而是在table中找到它并更新单元格的内容:

...
row.children().eq(1).text($(this).val())
...

row变量是一个 jQuery 对象,包含表中产品的tr元素。我通过位置(使用index方法)访问td元素,然后使用text方法设置其内容。

应用主题样式

篮子的功能性还好,但是外观很糟糕。幸运的是,jQuery UI 提供了一个 CSS 样式的框架,您可以将它应用于元素,使它们具有与主题应用于小部件相同的视觉外观。清单 26-4 显示了对文档中 HTML 元素的一些简单添加。

清单 26-4 。将 jQuery UI CSS 框架样式应用于表格元素

...
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="productWrapper">
            <div id="products"></div>
        </div>
        <div id="basket"class="ui-widget ui-widget-content">
            <table border=0id="basketTable">
                <trclass="ui-widget-header">
                    <th>Product</th><th>Quantity</th><th>Remove</th></tr>
                <tr id="placeholder"><td colspan=3>No Products</td></tr>
            </table>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
...

你可能已经注意到我在前面章节的一些例子中使用了ui-widget类。这是基本的 jQuery UI 样式,它应用于元素集的外部容器,这些元素集需要与 jQuery UI 小部件一致的外观。ui-widget-content类用于包含内容的元素,而ui-widget-header正如其名称所示,用于标题元素。

image 提示我在第三十五章中描述了 jQuery UI CSS 框架类。

除了应用这些类,我还禁用了table元素的边框,如下所示:

...
#basketTable {border: none; border-collapse: collapse}
...

你可以在图 26-7 中看到效果。

9781430263883_Fig26-07.jpg

图 26-7 。将 jQuery UI CSS 框架类应用到表中

更广泛地应用 CSS 框架

您可以更进一步,更广泛地应用框架风格。清单 26-5 显示了对文档的一些有用的补充。

清单 26-5 。更广泛地应用框架风格

...
<body>
    <div id="logoWrapper" class="ui-widget ui-widget-content ui-corner-all">
        <h1 id="logo">Jacqui's Flower Shop</h1>
    </div>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="productWrapper">
            <div id="products"></div>
        </div>
        <div id="basket" class="ui-widget ui-widget-content">
            <table border=0 id="basketTable">
                <tr class="ui-widget-header">
                    <th>Product</th><th>Quantity</th><th>Remove</th></tr>
                <tr id="placeholder"><td colspan=3>No Products</td></tr>
            </table>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
...

我已经将h1元素放在了一个div中,并应用了几个框架样式,包括ui-corner-all,这创建了你在图 26-8 中可以看到的圆角。我还在这个文档中应用了一些新的样式来创建我想要的效果,覆盖了从第三章开始使用的styles.css文件中的样式:

...
<style type="text/css">
    .dcell img {height: 60px}
    #basketTable {border: none; border-collapse: collapse}
    th, td {padding: 4px; width: 50px}
    td:first-child, th:first-child {width: 150px}
    #placeholder {text-align: center}
    #productWrapper {float: left; width: 65%}
    #basket {width: 33%; text-align: left; float: left; margin-left: 10px;
        position: absolute; right: 10px}
    #buttonDiv {clear: both}
    #logo {font-size: 1.5em; background-size: contain; margin: 1px;
        border: none; color: inherit}
    #logoWrapper {margin-bottom: 5px}
</style>
...

9781430263883_Fig26-08.jpg

图 26-8 。将 CSS 框架样式应用到文档标题

将圆角应用于桌子

ui-corner-all类应用到table元素会导致一些问题,如图图 26-9 所示。你会注意到table元素没有圆角。这是由 jQuery UI CSS 框架类和大多数浏览器中处理表格的方式之间的交互引起的。

9781430263883_Fig26-09.jpg

图 26-9 。桌子上圆角的效果

为了解决这个问题,您需要更改table元素,稍微不同地应用 jQuery UI CSS 框架类,并定义一个新的定制样式。首先,您需要修改table,如清单 26-6 所示。

清单 26-6 。修改表格元素以支持圆角

...
<form method="post" action="http://node.jacquisflowershop.com/order">
    <div id="productWrapper">
        <div id="products"></div>
    </div>
    <div id="basket" class="ui-widget ui-widget-contentui-corner-all">
        <table border=0 id="basketTable">
            <thead id="theader" class="ui-widget-header">
                <tr>
                    <thclass="ui-corner-tl">Product</th>                    <th>Quantity</th>
                    <thclass="ui-corner-tr">Remove</th></tr>
            </thead>
            <tr id="placeholder"><td colspan=3>No Products</td></tr>
        </table>
    </div>
    <div id="buttonDiv"><button type="submit">Place Order</button></div>
</form>
...

我在table中添加了一个thead元素,将标题和正文行分开。给thead元素分配一个id并应用ui-widget-header类是很重要的。因为 header 是ui-widget-header类的一部分,所以可以将其从tr元素中移除。

接下来,将ui-corner-tlui-corner-tr类应用于标题行的外部单元格。这些类为它们被分配到的元素的左上角和右上角创建圆角。(我在第三十五章中描述了所有的 jQuery UI CSS 框架类。)

接下来,您需要使用赋予thead元素的id来禁用style元素中的 CSS border属性,并对table元素执行相同的操作,如下所示:

...
<style type="text/css">
    .dcell img {height: 60px}
    #basketTable {border: none; border-collapse: collapse}
    th, td {padding: 4px; width: 50px}
    td:first-child, th:first-child {width: 150px}
    #placeholder {text-align: center}
    #productWrapper {float: left; width: 65%}
    #basket {width: 33%; text-align: left; float: left; margin-left: 10px;
        position: absolute; right: 10px}
    #buttonDiv {clear: both}
    #logo {font-size: 1.5em; background-size: contain; margin: 1px;
        border: none; color: inherit}
    #logoWrapper {margin-bottom: 5px}
    #theader {border: none}
</style>
...

最后,您需要对removeTableRow函数做一点小小的调整。既然您已经分离了标题行并将其放入了一个thead元素中,那么在tbody中就少了一行。这是零钱:

...
function removeTableRow(row) {
    row.remove();
    if ($("#basketTable tbody").children(":visible").length == 0) {
        $("#placeholder").show();
    }
}
...

image 提示tbody元素是浏览器在解析表格元素时自动创建的。HTML 的一个奇怪之处是,您不必指定这个元素(尽管如果愿意,您可以指定)。

有了这些改变,你就有了一个与文档中其他元素相匹配的圆角表格,如图 26-10 所示。

9781430263883_Fig26-10.jpg

图 26-10 。圆角桌子

创建 jQuery UI 按钮

下一步是重新定位按钮,并将其转换为 jQuery UI 小部件。清单 26-7 显示了对文档的修改。

清单 26-7 。按钮的重新定位和变形

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        .dcell img {height: 60px}
        #basketTable {border: none; border-collapse: collapse}
        th, td {padding: 4px; width: 50px}
        td:first-child, th:first-child {width: 150px}
        #placeholder {text-align: center}
        #productWrapper {float: left; width: 65%}
        #basket {text-align: left;}
        #buttonDiv {clear: both; margin: 5px}
        #logo {font-size: 1.5em; background-size: contain; margin: 1px;
            border: none; color: inherit}
        #logoWrapper {margin-bottom: 5px}
        #theader {border: none}
    </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="rowTmpl" type="text/x-handlebars-template">
        <tr id="{{name}}"><td>{{product}}</td><td>{{val}}</td>
            <td><a href="#">Remove</a></td>
        </tr>
    </script>
    <script type="text/javascript">
        $(document).ready(function () {

            $.getJSON("mydata.json", function (data) {

                var flowers = $("#flowerTmpl").template({ flowers: data }).filter("*");

                var rowCount = 1;
                for (var i = 0; i < flowers.length; i += 2) {
                    $("<a>").text(data[i].name + " & " + data[i + 1].name)
                        .appendTo("<h2>").parent().appendTo("#products");
                    $("<div>").attr("id", "row" + (rowCount++))
                        .appendTo("#products")
                        .append(flowers.slice(i, i + 2));
                }
                $("#products").accordion();

                $("input").change(function (event) {
                    $("#placeholder").hide();
                    var fname = $(this).attr("name");
                    var row = $("tr[id=" + fname + "]");

                    if (row.length == 0) {
                        $("#rowTmpl").template({
                            name: fname,
                            val: $(this).val(),
                            product: $(this).siblings("label").text()
                        }).appendTo("#basketTable").find("a").click(function () {
                            removeTableRow($(this).closest("tr"));
                            var iElem = $("#products").find("input[name=" + fname + "]");
                            $("#products").accordion("option", "active",
                                iElem.closest("div[id^=row]").index("div[id^=row]"));
                            iElem.val(0).select();
                        });
                    } else if ($(this).val() != "0") {
                        row.children().eq(1).text($(this).val());
                    } else {
                        removeTableRow(row);
                    }
                });
            });

            $("#buttonDiv, #basket").wrapAll("<div>").parent().css({
                float: "left",
                marginLeft: "2px"
            });

            $("button").button();

            function removeTableRow(row) {
                row.remove();
                if ($("#basketTable tbody").children(":visible").length == 0) {
                    $("#placeholder").show();
                }
            }
        });
    </script>
</head>
<body>
    <div id="logoWrapper" class="ui-widget ui-widget-content ui-corner-all">
        <h1 id="logo">Jacqui's Flower Shop</h1>
    </div>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="productWrapper">
            <div id="products"></div>
        </div>
        <div id="basket" class="ui-widget ui-widget-content ui-corner-all">
            <table border=0 id="basketTable">
                <thead id="theader" class="ui-widget-header">
                    <tr>
                        <th class="ui-corner-tl">Product</th>
                        <th>Quantity</th>
                        <th class="ui-corner-tr">Remove</th></tr>
                </thead>
                <tr id="placeholder"><td colspan=3>No Products</td></tr>
            </table>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

我将buttonDivbasket元素包装在一个新的div元素中,并调整了一些 CSS 样式来调整这些元素的位置。并且,如图 26-11 所示,我调用button方法来创建一个 jQuery UI 按钮。

9781430263883_Fig26-11.jpg

图 26-11 。重新定位和变换按钮元素

添加完成对话框

当用户单击 Place Order 按钮时,我想从他们那里收集一些额外的信息。在第二十章中,我向你展示了如何使用标签显示多部分表单,所以为了更多样化,这次我将使用一个对话框小部件。清单 26-8 显示了对话框文档的变化。

清单 26-8 。添加对话框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <link rel="stylesheet" type="text/css" href="styles.css"/>

    <style type="text/css">
        .dcell img {height: 60px}
        #basketTable {border: none; border-collapse: collapse}
        th, td {padding: 4px; width: 50px}
        td:first-child, th:first-child {width: 150px}
        #placeholder {text-align: center}
        #productWrapper {float: left; width: 65%}
        #basket {text-align: left;}
        #buttonDiv {clear: both; margin: 5px}
        #logo {font-size: 1.5em; background-size: contain; margin: 1px;
            border: none; color: inherit}
        #logoWrapper {margin-bottom: 5px}
        #theader {border: none}
        #completeDialog input {width: 150px; margin-left: 5px; text-align: left}
        #completeDialog label {width: 60px; 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="rowTmpl" type="text/x-handlebars-template">
        <tr id="{{name}}"><td>{{product}}</td><td>{{val}}</td>
            <td><a href="#">Remove</a></td>
        </tr>
    </script>
    <script type="text/javascript">
        $(document).ready(function () {

            $.getJSON("mydata.json", function (data) {

                var flowers = $("#flowerTmpl").template({ flowers: data }).filter("*");

                var rowCount = 1;
                for (var i = 0; i < flowers.length; i += 2) {
                    $("<a>").text(data[i].name + " & " + data[i + 1].name)
                        .appendTo("<h2>").parent().appendTo("#products");
                    $("<div>").attr("id", "row" + (rowCount++))
                        .appendTo("#products")
                        .append(flowers.slice(i, i + 2));
                }
                $("#products").accordion();

                $("#products input").change(function (event) {
                    $("#placeholder").hide();
                    var fname = $(this).attr("name");
                    var row = $("tr[id=" + fname + "]");

                    if (row.length == 0) {
                        $("#rowTmpl").template({
                            name: fname,
                            val: $(this).val(),
                            product: $(this).siblings("label").text()
                        }).appendTo("#basketTable").find("a").click(function () {
                            removeTableRow($(this).closest("tr"));
                            var iElem = $("#products").find("input[name=" + fname + "]");
                            $("#products").accordion("option", "active",
                                iElem.closest("div[id^=row]").index("div[id^=row]"));
                            iElem.val(0).select();
                        });
                    } else if ($(this).val() != "0") {
                        row.children().eq(1).text($(this).val());
                    } else {
                        removeTableRow(row);
                    }
                });
            });

            $("#buttonDiv, #basket").wrapAll("<div>").parent().css({
                float: "left",
                marginLeft: "2px"
            });

            $("button").button();

            $("#completeDialog").dialog({
                modal: true,
                buttons: [{ text: "OK", click: sendOrder },
                          {
                              text: "Cancel", click: function () {
                                  $("#completeDialog").dialog("close");
                              }
                          }]
            });

            function sendOrder() {

            }

            function removeTableRow(row) {
                row.remove();
                if ($("#basketTable tbody").children(":visible").length == 0) {
                    $("#placeholder").show();
                }
            }
        });
    </script>
</head>
<body>
    <div id="logoWrapper" class="ui-widget ui-widget-content ui-corner-all">
        <h1 id="logo">Jacqui's Flower Shop</h1>
    </div>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="productWrapper">
            <div id="products"></div>
        </div>
        <div id="basket" class="ui-widget ui-widget-content ui-corner-all">
            <table border=0 id="basketTable">
                <thead id="theader" class="ui-widget-header">
                    <tr>
                        <th class="ui-corner-tl">Product</th>
                        <th>Quantity</th>
                        <th class="ui-corner-tr">Remove</th></tr>
                </thead>
                <tr id="placeholder"><td colspan=3>No Products</td></tr>
            </table>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
    <div id="completeDialog" title="Complete Purchase">
        <div><label for="name">Name: </label><input name="first" /></div>
        <div><label for="email">Email: </label><input name="email" /></div>
        <div><label for="city">City: </label><input name="city" /></div>
    </div>
</body>
</html>

我添加了一个div元素,它的内容将在body元素中显示给用户,同时添加了一些 CSS 样式来覆盖使用link元素导入到文档中的styles.css文件中的样式。下面是对创建对话框小部件的dialog方法的调用:

...
$("#completeDialog").dialog({
    modal: true,
    buttons: [{ text: "OK", click: sendOrder },
                {
                    text: "Cancel", click: function () {
                        $("#completeDialog").dialog("close");
                    }
                }]
});
...

我创建了一个有两个按钮的模态对话框。单击取消按钮将关闭对话框。点击确定按钮将调用sendOrder功能。这个函数目前不做任何事情。

正如你在第二十二章中所记得的,对话框部件默认是打开的,这意味着它一创建就显示给用户。你可以在图 26-12 中看到它是如何出现的。

9781430263883_Fig26-12.jpg

图 26-12 。用于完成购买的对话框

image 提示注意,当我在input元素上设置change事件时,我缩小了选择范围。我将选择限制为排除对话框中的那些输入元素。如果我没有这样做,在完成购买对话框中输入一个值就会在购物篮中添加一个新的项目。

点击处理下单按钮

我不想让用户看到对话框,直到他们点击下订单按钮。我使用autoOpen设置来隐藏对话框,直到需要的时候,并使用click方法来处理按钮点击,如清单 26-9 所示。

清单 26-9 。隐藏对话框和处理按钮单击

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

        $.getJSON("mydata.json", function (data) {

            var flowers = $("#flowerTmpl").template({ flowers: data }).filter("*");

            var rowCount = 1;
            for (var i = 0; i < flowers.length; i += 2) {
                $("<a>").text(data[i].name + " & " + data[i + 1].name)
                    .appendTo("<h2>").parent().appendTo("#products");
                $("<div>").attr("id", "row" + (rowCount++))
                    .appendTo("#products")
                    .append(flowers.slice(i, i + 2));
            }
            $("#products").accordion();

            $("#products input").change(function (event) {
                $("#placeholder").hide();
                var fname = $(this).attr("name");
                var row = $("tr[id=" + fname + "]");

                if (row.length == 0) {
                    $("#rowTmpl").template({
                        name: fname,
                        val: $(this).val(),
                        product: $(this).siblings("label").text()
                    }).appendTo("#basketTable").find("a").click(function () {
                        removeTableRow($(this).closest("tr"));
                        var iElem = $("#products").find("input[name=" + fname + "]");
                        $("#products").accordion("option", "active",
                            iElem.closest("div[id^=row]").index("div[id^=row]"));
                        iElem.val(0).select();
                    });
                } else if ($(this).val() != "0") {
                    row.children().eq(1).text($(this).val());
                } else {
                    removeTableRow(row);
                }
            });
        });

        $("#buttonDiv, #basket").wrapAll("<div>").parent().css({
            float: "left",
            marginLeft: "2px"
        });

        $("button").button().click(function (e) {
            e.preventDefault();
            if ($("#placeholder:visible").length) {

                $("<div>Please select some products</div>").dialog({
                    modal: true,
                    buttons: [{
                        text: "OK",
                        click: function () { $(this).dialog("close") }
                    }]
                })
            } else {
                $("#completeDialog").dialog("open");
            }
        });

        $("#completeDialog").dialog({
            modal: true,
            autoOpen: false,
            buttons: [{ text: "OK", click: sendOrder },
                        {
                            text: "Cancel", click: function () {
                                $("#completeDialog").dialog("close");
                            }
                        }]
        });

        function sendOrder() {

        }

        function removeTableRow(row) {
            row.remove();
            if ($("#basketTable tbody").children(":visible").length == 0) {
                $("#placeholder").show();
            }
        }
    });
</script>
...

当用户点击按钮时,我会检查placeholder元素是否可见。我使用 jQuery 来实现这一点,使用一个选择器来生成一个jQuery对象,该对象仅在占位符可见时包含元素。

我使用占位符的可见性作为用户选择一些产品的代理。如果购物篮中有任何选择,占位符是隐藏的,因此一个可见的占位符告诉我没有选择。

image 提示这是一个很好的例子,展示了如何在文档中对功能进行分层,但这确实意味着我对产品选择的简单测试依赖于购物篮的实现,如果我修改了购物篮的工作方式,就需要进行修改。

如果用户没有选择任何产品就点击按钮,我会动态地创建并显示一个对话框小部件。你可以在图 26-13 中看到这是如何出现的。如果已经进行了选择,那么将显示完成对话框,以获取我希望从用户那里获得的最终信息。

9781430263883_Fig26-13.jpg

图 26-13 。如果没有产品选择,则显示一个对话框

完成订单

剩下的工作就是实现sendOrder函数。我已经向您展示了通过 Ajax 向服务器发送数据的不同方式,所以为了简化这一章,我将简单地从各种输入元素中收集值,并创建一个可以发送到服务器进行处理的 JSON 对象。清单 26-10 显示了对文档的补充。

清单 26-10 。完成订单流程

...
function sendOrder() {
    var data = new Object();
    $("input").each(function(index, elem) {
        var jqElem = $(elem);
        data[jqElem.attr("name")] = jqElem.val();
    })
    console.log(JSON.stringify(data));
    $("#completeDialog").dialog("close");
    $("#products input").val("0");
    $("#products").accordion("option", "active", 0)
    $("#basketTable tbody").children(":visible").remove();
    $("#placeholder").show();
}
...

在这个函数中,我从每个input元素中获取值,并将它们作为属性添加到一个对象中,然后我将其转换为 JSON 并写入控制台。

更有用的是,我然后重置文档,关闭对话框,重置input元素的值,切换到手风琴的第一个面板,并重置篮子。图 26-14 显示了一些产品选择的文件。我将使用这些来生成 JSON 字符串。

9781430263883_Fig26-14.jpg

图 26-14 。使用示例文档选择产品

当我点击“下订单”按钮时,会出现一个对话框,要求提供更多信息,如图图 26-15 所示。

9781430263883_Fig26-15.jpg

图 26-15 。提供附加信息完成订单

最后,单击 OK 按钮生成 JSON 并重置文档。这个例子的 JSON 如下:

{"aster":"12","daffodil":"7","rose":"5","peony":"2","primula":"0","snowdrop":"0",
 "first":"Adam Freeman","email":"adam@my.com","city":"London"}

如图 26-16 所示,你又回到了起点,准备再次经历这个过程。

9781430263883_Fig26-16.jpg

图 26-16 。重置文档

摘要

在这一章中,我重构了示例文档,加入了 jQuery UI 的特性。我添加了一些小部件,比如 accordion、dialog 和 button,并初步介绍了如何应用 jQuery UI CSS 框架类来管理其他元素的外观。我在第三十五章中给出了关于这些 CSS 类的更多细节。在第五部分中,我将转向 jQuery Mobile,您可以使用它来创建面向移动设备的 web 应用。

二十七、jQuery Mobile 入门

在这一章中,我将向您展示如何获得 jQuery Mobile 并将其添加到 HTML 文档中。我还解释了 jQuery Mobile 采用不同的方法来创建小部件,以及您必须如何适应这种方法。支持触摸的设备为 web 应用开发人员带来了一些独特的挑战,我将解释 jQuery Mobile 提供的一些核心特性,以帮助简化开发过程,并为移动 web 应用的开发和测试提供一些通用指南。表 27-1 提供了本章的总结。

表 27-1 。章节总结

问题 解决办法 列表
将 jQuery Mobile 添加到 HTML 文档。 添加一个script元素来导入 jQuery 和 jQuery Mobile 库,添加一个link元素来导入 CSS。 one
创建一个 jQuery Mobile 页面。 使用值为pagedata-role属性。 Two
禁用浏览器虚拟页面。 配置视口。 three
将定制 JavaScript 代码的执行推迟到 jQuery Mobile 增强文档之后。 使用pageinit事件。 four
简化触摸事件处理。 使用 jQuery Mobile 手势和虚拟鼠标事件。 5–7
响应设备方向的变化。 处理orientationchange事件或使用 CSS 媒体查询。 8, 9

image 注意正如我在第一章中所解释的,jQuery Mobile API 随着 1.3 版本的发布而发生了变化,我将在接下来的章节中重点介绍这些变化。

设置 jQuery Mobile

首先,我将向您展示如何获取和安装 jQuery Mobile。jQuery Mobile 是在 jQuery 和 jQuery UI 的基础上构建的,因此您将看到一些与这些库一致的常见模式。

获取 jQuery Mobile

你首先需要的是 jQuery Mobile,从http://jquerymobile.com开始就有了。在我写这篇文章时,jQuery Mobile 的当前版本是 1.3.1,您可以从下载页面获得一个 zip 文件。对于版本 1.3.1,这被称为jquery.mobile-1.3.1.zip

image 提示就像 jQuery 和 jQuery UI 一样,jQuery Mobile 可以通过内容分发网络(CDN)加载。我在第五章中描述了 cdn,它们对于部署在互联网上的 web 应用来说是一个很好的想法(但是很少用于内部网应用)。jQuery Mobile 下载页面包含了通过 CDN 使用 jQuery Mobile 所需的详细链接。

创建主题

jQuery Mobile 支持一个主题框架,它类似于 jQuery UI 使用的主题框架的简化版本。jQuery Mobile 包中包含一个默认主题,但是如果您想要创建一个自定义主题,您可以在http://jquerymobile.com/themeroller创建。使用 ThemeRoller 应用会生成一个 zip 文件,其中包含要包含在 web 文档中的 CSS 文件。我在第二十八章中描述了如何使用主题框架,但是我将在本书的这一部分使用默认主题,而不是创建一个自定义主题——部分原因是 jQuery Mobile ThemeRoller 没有一个方便的主题库。

获取 jQuery

还需要 jQuery。jQuery Mobile 1 . 3 . 1 版只适用于 jQuery 1 . 7 . 0 到 1.9.1 版。jQuery Mobile 往往落后于 jQuery 版本,在我写这篇文章时,对 jQuery 2.0 的支持还不可用。但是,您可以毫无问题地使用最新的 1.x 版本,因此我将使用 jQuery 1.10.1 作为本书这一部分的示例。

image 提示即使 jQuery Mobile 是基于 jQuery UI 构建的,你也不需要安装 jQuery UI 库。您需要的一切都包含在 jQuery Mobile 下载中。

正在安装 jQuery Mobile

您需要将 jQuery Mobile 下载中的三个项目复制到您提供 web 内容的目录中:

  • jquery.mobile-1.3.1.js文件(jQuery Mobile JavaScript 库)
  • jquery.mobile-1.3.1.css文件(jQuery Mobile 使用的 CSS 样式)
  • images目录(jQuery Mobile 图标)

当然,您还需要 jQuery 库,一旦一切就绪,您就可以创建一个使用 jQuery Mobile 的 HTML 文档。和前面的章节一样,我将我的文件命名为example.html,并将它保存在与前面列表中的项目相同的目录中。清单 27-1 显示了这个文件的内容。

清单 27-1 。example.html 的内容

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
</head>
<body>
    <div data-role="page">
        <div data-role="header">
                <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is Jacqui's Flower Shop
            <p><button>Press Me</button></p>
        </div>
   </div>
</body>
</html>

我突出显示的元素是 jQuery Mobile 所必需的。两个script元素导入 jQuery 和 jQuery Mobile JavaScript 库,link元素导入 jQuery Mobile 依赖的 CSS。因为我的 HTML 文件与 JavaScript 和 CSS 文件在同一个目录中,所以我可以简单地通过名称来引用这些文件。

image 提示暂时忽略文档的其余部分。我将简单解释一下meta元素的作用以及body元素的内容。

了解 jQuery Mobile 方法

虽然 jQuery Mobile 是基于 jQuery UI 的,但是您需要了解一些重要的区别。在您开始深入研究 jQuery Mobile 的功能之前,我需要解释一下这些差异,以便为后面几章中的信息提供一个背景。

分层支持

jQuery Mobile 为不同的移动浏览器提供了不同级别的支持。有三种级别的支持,每一种都有一个长长的支持设备和浏览器列表。在高端, A 级支持提供了最丰富的体验,并实现了我在本书这一部分描述的所有功能。

B 级支持提供了除 Ajax 导航之外的一切,我在第二十八章中描述了 Ajax 导航。这仍然是一个很好的功能水平,但是应用中页面之间的移动不会像 A 级设备那样流畅。

C 级品类基本。只有旧设备才属于这一类,jQuery Mobile 无法为这些设备提供太多的功能。

令人高兴的是,大多数现代移动设备都属于 A 级支持。您可以在http://jquerymobile.com/gbs看到支持设备的详细列表。

了解自动增强功能

使用 jQuery Mobile 时最显著的区别是,小部件不必显式创建。当使用 jQuery UI 时,您使用 jQuery 来选择一个或多个元素,然后应用诸如buttontabs之类的方法在文档中创建特定的 jQuery UI 小部件。如果你看一下清单 27-1 ,你会注意到我没有在文档中添加一个script元素来创建任何小部件。仅有的script元素用于导入 jQuery 和 jQuery Mobile 库。然而,如图 27-1 所示,我得到了格式化的内容。(该图显示了我在本书的这一部分广泛使用的 Opera 移动仿真器,我将在本章的后面适当地介绍它。)

9781430263883_Fig27-01.jpg

图 27-1 。该示例文档

image 注意对于本书这一部分中的大多数图形,我将使用横向分辨率的 Opera 移动浏览器模拟器,这让我可以在每页中装入更多的示例。

当您将 jQuery Mobile 库放入带有script元素的网页时,页面会自动增强。首先,jQuery Mobile 寻找具有data-role属性的元素。这些属性的值告诉 jQuery Mobile 应该对元素进行哪些增强。清单 27-2 突出显示了示例文档中的data-role属性。

image 提示名称以数据- 开头的属性称为数据属性。一段时间以来,数据属性一直是定义自定义属性的非正式约定,并且已经包含在 HTML5 的官方标准中。

清单 27-2 。示例文档中的数据角色属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
</head>
<body>
    <divdata-role="page">
        <divdata-role="header">
                <h1>Jacqui's Shop</h1>
        </div>
        <divdata-role="content">
            This is Jacqui's Flower Shop
            <p><button>Press Me</button></p>
        </div>
   </div>
</body>
</html>

jQuery Mobile 的一个不寻常的特性是一个 HTML 文档可以包含多个页面(我在第二十八章中演示了这个特性)。页面是 jQuery Mobile 应用的构建块。在这个例子中只有一个页面,它由元素div表示,元素data-role的值是page。因为页面嵌套在 HTML 文档中,所以您还需要向 jQuery Mobile 提供关于页面中包含的元素的用途的附加信息。还有另外两个data-role属性,告诉 jQuery Mobile 哪个元素包含页面的标题信息,哪个元素包含内容。表 27-2 总结了本例中的三个data-role值及其意义。您可以很容易地将div元素及其data-role值与图 27-1 中所示的页面结构相关联。

表 27-2 。示例文档中的数据角色属性值

价值 描述
page 告诉 jQuery Mobile 将元素的内容视为页面。
header 告诉 jQuery Mobile 该元素表示页面标题。
content 告诉 jQuery Mobile 元素包含页面的内容。

image 提示 jQuery Mobile 会自动为页面的内容部分插入包装器。这意味着不属于另一部分的任何元素都被视为内容,从而允许您显式跳过为该部分定义元素。

您不必采取任何显式的操作来让 jQuery Mobile 找到具有data-role属性的元素并生成页面。这一切都是在加载 HTML 文档时自动发生的。有些元素,比如button,是自动样式化的(尽管,正如我在后面的章节中演示的,您可以使用其他数据属性配置大多数小部件)。

image 提示 jQuery Mobile 不遗余力地减少创建移动 web 应用所需的定制 JavaScript 的数量。事实上,根本不需要任何定制的 JavaScript 就可以创建简单的应用。然而,这并不意味着您可以为禁用了 JavaScript 的浏览器构建 jQuery Mobile 应用。jQuery Mobile 是一个 JavaScript 库,需要 JavaScript 支持来执行页面的自动增强。

了解视口

虽然不是 jQuery Mobile 的一部分,但添加到 HTML 文档中的一个重要元素是清单 27-3 中突出显示的元素。

清单 27-3 。配置视口的元元素

...
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
</head>
...

我突出显示了名称属性为viewportmeta元素。许多移动浏览器使用虚拟页面来显示网页内容,以提高与桌面浏览器设计的网站的兼容性。这通常是一个明智的想法,因为它为用户提供了页面结构的整体感觉,即使细节太小而无法阅读。图 27-2 显示了 jQuery Mobile 主页的初始显示和缩放,以便文本可读。

9781430263883_Fig27-02.jpg

图 27-2 。移动浏览器虚拟页面

第一个框架以纵向显示了 jQuery Mobile web 站点(这突出了效果)。文本太小,无法阅读,但移动浏览器支持放大页面区域,如第二帧所示。诚然,虚拟页面是一种妥协,但考虑到为移动设备定制的网站相对较少,这是可以理解的。

问题是虚拟页面的应用没有太多的区别,这给 jQuery Mobile 应用带来了问题。图 27-3 显示了当使用虚拟页面时,示例文档是如何显示的。

9781430263883_Fig27-03.jpg

图 27-3 。在宽虚拟页面中显示的示例文档

如图所示,jQuery Mobile 元素显示得非常小,以至于无法使用。示例文档中的meta元素告诉浏览器页面的宽度应该是屏幕的宽度。这导致浏览器以合理的大小显示您的 jQuery Mobile 元素。

了解 jQuery Mobile 事件

关于与 jQuery Mobile 相关的事件,有两条重要的信息。在接下来的小节中,我将对它们进行描述。

了解页面事件

jQuery Mobile 定义了一系列描述页面生命周期的事件。其中最重要的是pageinit事件。jQuery Mobile 通过注册其函数来处理 jQuery ready事件,自动增强页面,您在本书的前面部分已经依赖了该事件。如果您想在文档中包含定制的 JavaScript,您必须注意在 jQuery Mobile 处理完文档之前不要执行您的代码。这意味着您必须等待pageinit事件,然后在 jQuery Mobile 完成文档初始化时触发该事件。没有像ready事件那样方便的方法,所以你必须使用bind方法将你的函数与事件关联起来,如清单 27-4 所示。

清单 27-4 。使用 pageinit 事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function () {
            $("button").click(function () {
                console.log("Button pressed")
            })
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
</head>
<body>
    <div data-role="page">
        <div data-role="header">
                <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is Jacqui's Flower Shop
            <p><button>Press Me</button></p>
        </div>
   </div>
</body>
</html>

bind方法的参数是您感兴趣的事件的名称以及当事件被触发时应该执行的函数。只有当您选择并应用了bind方法的元素的事件被触发时,您的函数才会被执行。

在这个例子中,我使用了bind方法来注册一个函数,这个函数将在pageinit事件被触发时执行。在这个函数中,我放置了希望在文档加载和处理后执行的语句。在本例中,我使用 jQuery 选择了文档中的button元素,并使用click方法注册了另一个在单击按钮时将执行的函数,正如我在本书中一直做的那样。

image 提示请注意,在将 jQuery Mobile JavaScript 库导入到文档之前,我已经插入了新的script元素。这对于pageinit事件来说并不重要,但是对于用于更改一些 jQuery Mobile 设置的mobileinit事件来说是必需的(我将在第二十八章中演示如何做到这一点)。我发现在导入 jQuery Mobile 库之前总是放置定制代码是一个好主意,即使我只是响应pageinit事件。

理解触摸事件

浏览器中的触摸事件有一个规范,但它是非常低级的,因为在触摸交互模型中有很多变化。例如,一些设备支持多点触摸,并且触摸手势的解释方式多种多样。表 27-3 描述了这些低级触摸事件。

表 27-3 。标准触摸事件

事件 描述
touchstart 当用户触摸屏幕时触发。对于多点触摸设备,每次手指触摸屏幕时都会触发此事件。
touchend 当用户将手指从屏幕上移开时触发。
touchmove 当用户在触摸屏幕时握住或移动手指时触发。
touchcancel 当触摸序列中断时触发。这是特定于设备的含义,但一个常见的例子是当用户将手指滑离屏幕边缘时。

解释这些事件并找出其意义的责任落在了开发人员身上。这是一项充满错误的痛苦任务,我建议你尽可能避免。这是 jQuery Mobile 可以帮助解决的问题,我将简单解释一下。

image 提示如果你确实想了解触摸事件的细节,那么你可以在www.w3.org/TR/touch-events找到规范。这包括事件的完整描述和可用于获得每个触摸交互细节的属性。

大多数网站在设计时都没有考虑到触摸事件。为了支持尽可能广泛的网站脚本,移动浏览器从触摸事件合成鼠标事件。这意味着浏览器触发触摸事件,然后生成相应的(假的)鼠标事件,这些事件代表相同的动作,但就好像它们是用传统鼠标执行的一样。清单 27-5 包含了一个有用的脚本,演示了这是如何实现的。

清单 27-5 。监控触摸和合成鼠标事件

<!DOCTYPE html>
<html>
<head>
    <title>Event Test</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <style type="text/css">
        table {border-collapse: collapse; border: medium solid black; padding: 4px}
        #placeholder {text-align: center}
        #countContainer * {display: inline; width:50px}
        th {width: 100px}
    </style>
    <script type="text/javascript">
        $(document).bind("pageinit", function() {
            var eventList = [
                "mousedown", "mouseup", "click", "mousecancel",
                "touchstart", "touchend", "touchmove", "touchcancel"]

            for (var i = 0; i < eventList.length; i++) {
                $("#pressme").bind(eventList[i], handleEvent)
            }

            $("#reset").bind("tap", function() {
                $("tbody").children().remove();
                $("#placeholder").show();
                startTime = 0;
            })
        });

        startTime = 0;
        function handleEvent(ev) {
            var timeDiff = startTime == 0 ? 0 : (ev.timeStamp - startTime);
            if (startTime == 0) {
                startTime = ev.timeStamp
            }
            $("#placeholder").hide();
            $("<tr><td>" + ev.type + "</td><td>" + timeDiff + "</td></tr>")
                .appendTo("tbody");
        }
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div data-role="page">
        <div data-role="content">
            <div id="tcontainer" class="ui-grid-a">
                <div class="ui-block-a">
                    <button id="pressme">Press Me</button>
                    <button id="reset">Reset</button>
                </div>
                <div class="ui-block-b">
                    <table border=1>
                        <thead>
                            <tr><th>Event</th><th>Time</th></tr>
                            <tr id="placeholder"><td colspan=2>No Events</td><tr>
                        </thead>
                        <tbody></tbody>
                    </table>
                </div>
            </div>
        </div>
   </div>
</body>
</html>

本例中有两个按钮和一个表格。“按我”按钮是有线连接的,因此当单击按钮时,选择的鼠标和触摸事件会显示在表格中。对于每个事件,显示事件类型和自上次事件以来的毫秒数。重置按钮清除表格并重置计时器。你可以在图 27-4 中看到效果。

9781430263883_Fig27-04.jpg

图 27-4 。观察触摸和鼠标事件的顺序

表 27-4 显示了在 Opera 移动浏览器中点击按钮时出现的事件序列和计时。

表 27-4 。来自 Opera Mobile 的事件序列

事件 相对时间
touchstart 0
touchend 96
mousedown 315
mouseup 315
click 321

你可以看到touchstarttouchend事件首先被触发,响应我触摸然后放开屏幕的瞬间。浏览器然后生成mousedownmouseup事件,然后生成一个click事件。注意,在触发touchendmousedown事件之间有相当大的延迟,大约 300 毫秒。这种延迟足以使依赖合成事件成为问题,因为您的 web 应用将落后于用户的触摸交互。不是所有的浏览器都有这个问题,但这是一个很常见的问题,我建议您在您打算使用的浏览器上测量延迟。

使用 jQuery Mobile 手势方法

jQuery Mobile 做了两件事来简化事件处理。第一个是一组手势事件,这些事件是响应特定的低级触摸事件序列而触发的,这意味着您不必自己分析触摸序列来理解用户正在做出的手势。这些事件在表 27-5 中描述。

表 27-5 。标准触摸事件

事件 描述
tap 当用户触摸屏幕,然后快速连续移开手指时触发。
taphold 当用户触摸屏幕,保持手指不动大约一秒钟,然后松开时触发。
swipe 当用户在一秒钟内执行至少 30 个像素的水平拖动,并且垂直变化小于 20 个像素时触发。
swipeleft 当用户向左滑动时触发。
swiperight 当用户向右滑动时触发。

这些事件使得处理基本手势变得简单。清单 27-6 将这些事件添加到计时示例中。

清单 27-6 。将 jQuery Mobile 手势事件添加到计时示例中

...
<script type="text/javascript">
    $(document).bind("pageinit", function() {
        var eventList = [
            "mousedown", "mouseup", "click", "mousecancel",
            "touchstart", "touchend", "touchmove", "touchcancel",
            "tap", "taphold", "swipe", "swipeleft", "swiperight"]

        for (var i = 0; i < eventList.length; i++) {
            $("#pressme").bind(eventList[i], handleEvent)
        }

        $("#reset").bind("tap", function() {
            $("tbody").children().remove();
            $("#placeholder").show();
            startTime = 0;
        })
    });

    startTime = 0;
    function handleEvent(ev) {
        var timeDiff = startTime == 0 ? 0 : (ev.timeStamp - startTime);
        if (startTime == 0) {
            startTime = ev.timeStamp
        }
        $("#placeholder").hide();
        $("<tr><td>" + ev.type + "</td><td>" + timeDiff + "</td></tr>")
            .appendTo("tbody");
    }
</script>
...

图 27-5 显示了当我点击浏览器中的按钮时会发生什么。

9781430263883_Fig27-05.jpg

图 27-5 。将 jQuery Mobile 手势事件添加到计时示例中

表 27-6 以易读的表格显示了事件顺序。因为我正在点击一个按钮,所以唯一出现的手势事件是tap。需要注意的重要一点是,点击事件被快速触发,通常在我从屏幕上松开的几毫秒内。

表 27-6 。来自 Opera Mobile 的事件序列

事件 相对时间
touchstart 0
touchend 63
轻点 63
mousedown 317
mouseup 321
click 328

手势事件的好处在于,jQuery Mobile 即使在不支持触摸事件的浏览器中,或者在没有触摸界面的设备上运行的浏览器中,也会触发手势事件。图 27-6 显示了运行在谷歌 Chrome 桌面浏览器中的例子。

9781430263883_Fig27-06.jpg

图 27-6 。桌面浏览器中的事件序列

表 27-7 更清楚地显示了事件的顺序及其相对时序。

表 27-7 。来自谷歌浏览器的事件序列

事件 相对时间
mousedown 0
mouseup 79
click 80
轻点 80

如你所料,这个序列中没有touchstarttouchend事件,事件的顺序也不同(因为鼠标事件是真实的,而不是合成的)。即便如此,tap事件仍然会在click事件之后立即触发。

image 提示我在移动 web 应用中使用tap事件而不是click,因为它避免了来自合成事件的计时问题,并且因为它也是在非触摸平台上生成的。

使用 jQuery Mobile 虚拟鼠标事件

浏览器不需要合成鼠标事件,这意味着在触摸和非触摸设备上工作的 web 应用应该监听鼠标事件和触摸事件。对于合成事件的移动浏览器,每次交互都有触摸和鼠标事件。为了帮助简化这个过程,jQuery Mobile 定义了一组虚拟鼠标事件。当您注册这些事件时,jQuery Mobile 会负责删除重复的事件,并确保触发适当的事件,而不管您是否支持触摸。表 27-8 描述了虚拟事件。

表 27-8 。标准触摸事件

事件 描述
vmouseover 响应mouseover事件时触发(因为用户的手指并不总是接触屏幕,所以没有等效的触摸事件)。
vmousedown 响应touchstartmousedown事件而触发。
vmousemove 响应touchmovemousemove事件而触发。
vmouseup 响应touchendmouseup事件而触发。
vclick 响应click事件而触发。
vmousecancel 响应touchcancelmousecancel事件而触发。

这些事件的生成方式创造了一个类似鼠标的序列,即使在触摸设备上也是如此。为了解释我的意思,我在计时示例中添加了一些虚拟事件,如清单 27-7 所示。

清单 27-7 。将 jQuery Mobile 虚拟事件添加到计时示例中

...
<script type="text/javascript">
    $(document).bind("pageinit", function() {
        var eventList = [
            "mousedown", "mouseup", "click", "mousecancel",
            "touchstart", "touchend", "touchmove", "touchcancel",
            "tap", "taphold", "swipe", "swipeleft", "swiperight",
            "vmouseover", "vmousedown", "vmouseup", "vclick", "vmousecancel"]

        for (var i = 0; i < eventList.length; i++) {
            $("#pressme").bind(eventList[i], handleEvent)
        }

        $("#reset").bind("tap", function() {
            $("tbody").children().remove();
            $("#placeholder").show();
            startTime = 0;
        })
    });

    startTime = 0;
    function handleEvent(ev) {
        var timeDiff = startTime == 0 ? 0 : (ev.timeStamp - startTime);
        if (startTime == 0) {
            startTime = ev.timeStamp
        }
        $("#placeholder").hide();
        $("<tr><td>" + ev.type + "</td><td>" + timeDiff + "</td></tr>")
            .appendTo("tbody");
    }
</script>
...

当我触摸屏幕时,jQuery Mobile 生成了vmouseovervmousedown事件。这些在纯触控环境下没有任何意义。如果您正在编写一个跨平台的应用,那么您可能希望在用户将桌面鼠标移动到某个元素上时执行一些操作。通过触发合成的vmouseover事件来响应真实的touchstart事件,您可以无缝地对触摸设备执行相同的操作。你可以在图 27-7 中看到结果。

9781430263883_Fig27-07.jpg

图 27-7 。将虚拟事件添加到计时示例中

表 27-9 以更易于阅读的形式显示了事件和时序。尽管vclick事件在合成click事件之前很久就被触发了,但情况并不总是如此,我不推荐使用vclick来代替click来解决事件延迟问题。

表 27-9 。来自谷歌浏览器的事件序列

事件 相对时间
touchstart 0
vmouseover 0
vmouedown 0
touchend 64
vmouseup 64
v 点击 64
tap 64
mousedown 320
mouseup 327
click 331

image 警告不要对现实和虚拟事件交错的方式做出假设,这一点很重要。这是因为非触摸设备上的事件序列会有所不同。虚拟事件相对于彼此的顺序是相同的;中间的真实事件可能会改变。

响应器件方向变化

大多数移动浏览器支持一个名为orientationchange的事件,每当设备旋转 90 度时就会触发该事件。为了让生活更简单,jQuery Mobile 会在浏览器不支持的时候合成orientationevent 。这是通过监听窗口大小的变化并查看新的高度和宽度值的比率来完成的。清单 27-8 展示了如何对这个事件做出响应。

清单 27-8 。响应方向的变化

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function() {
            $(window).bind("orientationchange", function(e) {
                $("#status").text(e.orientation)
            })
            $("#status").text(jQuery.event.special.orientationchange.orientation())
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div data-role="page">
        <div data-role="header">
                <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <p>Device orientation is: <b><span id=status></span></b></p>
        </div>
   </div>
</body>
</html>

为了绑定到orientationchange事件,您必须选择window对象。在这个例子中,我修改了一个表示新方向的span元素的文本。这个信息可以通过传递给处理函数的Event对象的orientation属性获得。

jQuery Mobile 还提供了一种确定当前方向的方法,如下所示:

...
jQuery.event.special.orientationchange.orientation()
...

我在示例中使用这个方法来设置span元素的内容,因为在处理页面时不会触发orientationchange事件,只有在设备随后被重定向时才会触发。

如果你没有一个真实的移动设备来测试这个例子,那么你可以使用我在本章后面描述的模拟器。它们中的大多数都有模拟旋转的能力,由特定的击键或按钮触发。对于我正在使用的 Opera 移动模拟器 ,按 Ctrl+Alt+R 触发效果,如图图 27-8 所示。

9781430263883_Fig27-08.jpg

图 27-8 。响应方向的变化

jQuery Mobile 产生的合成事件意味着,当您调整不支持方向更改的浏览器(如桌面浏览器)的窗口大小时,可以获得相同的效果。在这种情况下,方向由窗口的宽度和高度决定。

使用媒体查询来管理方向

orientationchange事件允许您使用 JavaScript 响应方向的变化。另一种方法是使用 CSS,对每个方向的元素应用不同的样式,这可以使用 CSS 媒体查询来实现。清单 27-9 展示了如何做到这一点。

清单 27-9 。使用 CSS 媒体查询响应方向

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        @media screen and (orientation:portrait) {
            #pstatus {display: none}
            #lstatus {display: inline}
        }

        @media screen and (orientation:landscape) {
            #pstatus {display: inline}
            #lstatus {display: none}
        }
    </style>
</head>
<body>
    <div data-role="page">
        <div data-role="header">
                <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <p>Device orientation is:
                <span id=pstatus><b>Portrait</b></span>
                <span id=lstatus><b>Landscape</b></span>
            </p>
        </div>
   </div>
</body>
</html>

CSS 媒体查询允许您定义在特定情况下应用的样式集,在这种情况下,方向是横向或纵向。我使用 CSS display属性来显示或隐藏元素,这允许我创建与前一示例中使用 JavaScript 相同的效果。在这种情况下,不需要任何形式的合成。针对方向的媒体查询同样适用于桌面浏览器和移动浏览器。

使用移动设备

为移动设备开发应用与常规的桌面开发有一些明显的不同。在接下来的部分中,我将提供一些指导和信息来帮助您开始,并强调您将面临的一些关键问题。

识别移动设备

如果你为桌面和移动用户提供一个应用,你可能想要定制你所展示的界面。一种常见的方法是为桌面浏览器提供 jQuery UI 接口,为移动设备提供 jQuery Mobile 接口。

困难在于识别哪些浏览器运行在移动设备上。有多种技术可以做到这一点,所有这些都在服务器端执行,将浏览器重定向到适当的 HTML 文档。我不打算深入讨论这些细节,因为它们超出了本书的范围。如果您是这个问题的新手,那么我建议您看看http://wurfl.sourceforge.net,它包含了一个有用的服务器端组件,可以识别大多数移动设备。您还应该考虑https://github.com/kaimallea/isMobile,它提供了一个客户端解决方案。

我建议不要根据用户的浏览器自动强迫用户使用移动版本的应用。一些用户更喜欢使用桌面版本的应用,甚至在移动设备上,特别是因为移动版本通常具有受限的功能。我的建议是,当用户到达你的站点时,如果你检测到一个移动设备,就给用户一个简单的选择,并且即使已经做出了最初的决定,也要使在你的应用版本之间切换变得容易。

避免移动开发的两大罪

在为移动设备构建 web 应用时,有两个陷阱需要避免:糟糕的假设不切实际的模拟。我解释了这两者,并提供了一些背景来帮助您避免犯一些常见的错误。

避免不良假设

移动设备市场非常活跃,相对不成熟,缺乏标准化。在为桌面构建 web 应用时,通常会做出一些假设(尽管通常不会明说)。人们普遍期望最低屏幕分辨率、JavaScript 支持、某些插件的可用性,以及用户能够使用鼠标点击和键盘输入文本。

这并不是说这些都是合理的假设。例如,如果您假设 JavaScript 可用,那么您就排除了那些没有(或不能)在浏览器中启用 JavaScript 的潜在客户。您可能会认为这是一个很好的权衡,大多数用户如果愿意都可以启用 JavaScript,而您将放弃不符合您所要求的规范的用户。

移动设备市场的情况更加复杂,因为这个市场非常分散。桌面空间可能看起来多种多样,但是 Mac、Windows PC 和 Linux box 都有很多共同点。对于移动设备来说就不一样了,关于屏幕大小、网络连接和输入法的假设将会消除一些大的市场份额。

世界不是一部 iPhone

我看到的最糟糕的假设之一是目标市场是 iPhone。iPhone 取得了巨大的成功,但它并不是移动设备市场的全部,甚至不同型号的 iPhone 之间也存在差异。常见的目标屏幕分辨率是 320×480 像素,这来自于较旧的 iPhone 型号。很多设备都有这种分辨率,但越来越多的设备没有。在你的移动应用中使用固定的屏幕分辨率只会淘汰那些屏幕太小的用户,并让那些支付了额外费用来获得更高分辨率设备的用户感到烦恼。

这个世界根本不是一部电话

另一个常见的假设是,目标市场是手机,忽略了平板电脑市场的成功。不仅平板电脑通常具有更高的屏幕分辨率,而且人们持有和使用它们的方式也不同。要明白我的意思,去任何一家咖啡店看看顾客。我的观察(完全不科学,但始终如一)是,更大尺寸的平板电脑让它们拿起来有点别扭,所以它们通常靠在别的东西上。这意味着它们有些不稳定,在屏幕上拖动手指会使平板电脑轻微抖动(使准确性成为问题),并模糊了屏幕的许多部分(因为用户的手和手臂在平板电脑本身上方)。

我的观点是,移动设备的本质在很大程度上决定了它们的使用方式,以及什么样的交互是明智和可取的。弄清楚这一点的最好方法是观察人们与一系列设备的交互。如果你有时间和金钱,可用性实验室是一个很好的资源。但是,即使你很匆忙,预算有限,在星巴克度过一个下午也能提供一些有价值的见解。

这个世界不支持触摸

并非所有的移动设备都有触摸屏。有些依靠微型鼠标结合键盘,有些有多种输入方法。我的测试机器之一是一台可以转换成平板电脑的小型笔记本电脑。它有一个触摸屏以及一个完整的键盘和鼠标。用户希望能够使用他们可用的最佳输入方法,对可用输入进行假设只会让用户感到沮丧(这也是我除了测试之外很少使用笔记本电脑/平板电脑组合设备的原因)。

移动带宽不是免费的,也不是无限的

网络连接的价格经历了几个周期,由网络用户执行的各种活动决定。目前,网络提供商正在努力筹集资金和建设足够的网络容量来满足需求,特别是在人口密集的城市地区。最终,容量成本将会下降,可用带宽将会增加,但目前,网络提供商对数据访问收取额外费用,并对用户每月可以下载的数据量设置较低的上限。

假设用户愿意将他们的大量数据贡献给你的 web 应用是很危险的。一般来说,客户并不像你希望的那样关心你的应用。听起来可能很伤人,但几乎总是真的。你的应用充满了你的世界,这是应该的,但对用户来说,它只是众多应用中的一个。

在第二十八章中,我将向您展示 jQuery Mobile 如何在用户需要之前预取 web 应用的内容。这是一个很棒的功能,但应该谨慎使用,因为它假设用户愿意在他们可能永远不需要的内容上花费带宽。自动和频繁的数据更新也是如此。请谨慎使用,并且只有当用户明确表示您的应用应该是他的网络配额的大量用户时才使用。

同样,不要对移动设备可用的数据速率做出假设。考虑一下您对图像和视频等大型资源的使用。一些用户将有能力快速下载这些内容,但许多人不会,以我的经验,低带宽的选择总是受欢迎的。

您还应该准备好应对网络不可用的情况。以前坐火车上下班,一进隧道网络就会掉线。一个编写良好的 web 应用会预料到连接问题,向用户报告这些问题,并在网络重新可用时优雅地恢复。可悲的是,大多数应用都写得不好。

image 提示我曾经为一家全球集装箱运输公司工作过,他们面临的约束促使他们开发了一些我见过的最健壮、适应性最强的应用。他们在世界上几乎每一个港口都有运输代理人,这些地方非常偏远,代理人的办公室只是码头尽头的一间小屋。他们可以将现代电脑运送到这些地方(这不成问题,因为他们是一家运输公司),但联网通常仅限于慢速拨号连接,在停电期间每天工作几个小时,这意味着连接建立之间可能需要几天时间。即使没有网络链接,每个应用都必须允许本地运输办公室继续工作,并且只要可以建立连接,就将本地数据与全球网络同步。这需要大量的思考和测试,但结果是 It 基础设施帮助他们主宰了全球集装箱运输。我在设计移动应用时经常会想到这些限制——现代设备通常可以期望更好的操作条件,但最好的应用总是希望最好的,假设最坏的,并代表用户处理问题。

避免不切实际的模拟和测试

移动设备的多样性意味着你必须进行彻底的测试。在开发的早期阶段使用实际的移动设备可能会令人沮丧。网络请求通过单元网络路由,这要求开发机器是公开可用的。一些移动设备有开发者模式,但是它们有自己的缺点。

简而言之,您需要一个模拟的环境来开始您的开发,这种环境能够让您快速方便地构建和测试,而不必将您的开发环境暴露给外界。幸运的是,有仿真器提供您需要的设施。我将在本章后面描述一些可用的选项,但它们分为两类。

第一类模拟器是将实际的移动浏览器移植到另一个平台上。浏览器的一切都尽可能的接近真实。第二类依赖于这样一个事实,即大多数浏览器为移动和桌面机器使用一个通用的渲染引擎。因此,举例来说,如果你想大致了解 iPhone 浏览器将如何处理文档,你可以使用苹果 Safari 浏览器,因为它有共同的根。模拟器只不过是一个可视化包装器和桌面渲染引擎周围的屏幕大小限制。

这两种方法都很有用,值得探索。我经常在移动产品开发的早期阶段使用它们。但是一旦我有了基本的功能,我就开始在真实设备上进行测试,随着项目接近完成,我切换到只使用真实设备,并完全停止使用模拟器。

这是因为模拟器有两个主要缺点。首先,他们的模拟并不是 100%准确。即使是最好的模拟器也不总是像使用相同浏览器的真实设备那样呈现内容。第二个——在我看来也是最重要的——失败在于触摸输入是模拟的。

鼠标用于使支持触摸的浏览器在非触摸桌面 PC 上工作,鼠标只是不能产生与手指相同的效果。桌面仿真中缺少三个触摸因素:触感阻碍不准确

触感的缺乏

缺乏触感意味着你不知道使用网络应用会有什么样的感觉。在玻璃显示屏上轻敲和滑动是一项奇怪的活动。当应用正确响应时,效果是优雅和令人愉快的。当应用滞后于输入或误解了触摸交互时,结果是令人沮丧的。鼠标不能给你反馈,告诉你如何处理触摸。

*没有阻碍

我已经提到了阻碍的问题。当你使用触摸设备时,即使是很小的设备,你的手指和手也会遮住部分屏幕。在为触摸设备设计 web 应用时,您需要考虑到这一点。你需要小心地放置控件,以便用户在触摸屏幕时仍然可以看到正在发生的事情,并且你需要记住,大约 10%的人是左撇子,因此屏幕的不同部分对这些用户来说是模糊的。只有亲手触摸按钮和链接,您才能真正理解您的 web 应用是多么容易使用。

image 提示如果你去咖啡店做一些用户观察,留意那些遵循独特模式的用户。他们触摸屏幕,然后将手完全移开一秒钟,然后收回手,做出另一个触摸手势。这通常表示应用已经定位了它的窗口小部件,使得从一个动作产生的视觉反馈在用户的手中。用户必须把她的手移开才能看到发生了什么,然后再移回来做另一个手势,这是一个令人疲惫和沮丧的经历。

缺乏不准确性

有了鼠标,用户可以异常准确地击中屏幕上的目标。每像素精度可以通过现代鼠标和一点点练习来实现。人的手指却不是这样,你所能期望的最大精度是“大致在目标的区域内”。这意味着你必须选择简单的小工具,并创建考虑到不准确性的布局。您无法感受到在模拟器中点击小部件是多么容易。鼠标不是一个足够好的代理。你需要在一系列不同的屏幕尺寸和分辨率上进行测试,以了解你的用户将会面对什么。这些信息提供了关于页面上小部件的大小和密度的重要线索。

一个不准确的个人故事

我个人的挫折感可以追溯到我过去乘火车上下班的时候。我住在英国,在那里,火车准时到达被视为一个无法实现的目标。在夏天,当火车晚点时,我真的不介意。我可以在阳光下逗留。我从来不想在冬天逗留,几分钟后我想查看火车会晚点多长时间,这可以通过在线应用实现。

想象一下这个场景。太阳还没出来,风很刺骨,地面冰冷。我被裹得暖暖的,但是我从车里带出来的热量正在迅速消退。我想加载应用,导航到本地电台的信息,并了解我将等待多长时间(如果要等一段时间,回到我的车上并考虑开车去办公室)。

我一脱下手套,手指就开始发冷。几分钟后,我不能正常弯曲手指,我的手开始颤抖,这很不幸,因为我需要点击的小部件很小。它们只是用小字体显示的普通网络链接。我从来没有设法轻松导航到我想要的信息。我会点击错误的链接,被迫等待加载错误的信息,然后导航回来再试一次。与此同时,随着我的手越来越冷,我准确点击小工具的能力越来越差。我变得讨厌假定像素精度的移动网络应用,特别是那个应用。

使用移动浏览器模拟器

尽管移动浏览器模拟器有其局限性,但它仍然有着重要的作用。在本书的前一版本中,我描述了一系列不同的模拟器以及每种模拟器的优缺点。从那以后,我的开发风格改变了,我只用两个工具:Opera 移动模拟器和 BrowserStack。

使用桌面浏览器测试移动应用

桌面浏览器显然与移动版本不同,但有共同的基础,在部署到真实设备之前,可以用于快速和肮脏的测试。当我已经有了应用的主要构件,并且正在充实功能区域时,我经常使用这些浏览器。使用桌面浏览器的主要好处是它们拥有优秀的开发工具,包括 JavaScript 调试器,并且在大多数情况下,桌面浏览器是移动项目早期阶段或当您试图跟踪代码或标记中的问题时的优秀开发工具。

使用 Opera 手机模拟器

我在项目的初始阶段使用 Opera Mobile 模拟器,这是我唯一做的测试。这个浏览器允许我模拟不同屏幕尺寸的设备,包括平板电脑和横向设备。

正在被仿真的 Opera 移动浏览器被广泛使用,仿真器在精确布局内容方面做得很合理(如果不是完美的话)。一些 jQuery Mobile 特性,比如导航转换(我在第二十八章的中描述过)不被支持。这是我在本章前面用来获取图形屏幕截图的模拟器。

这种测试的主要好处是速度快,允许我喜欢的快速编写和测试的开发风格。一个很好的特性是,您可以使用 Opera 桌面版内置的调试器来调试移动模拟器。设置这个的过程有点笨拙,但是它是一个有用的特性。Opera 移动模拟器在http://www.opera.com/developer/mobile-emulator免费提供。

使用浏览器堆栈

BrowserStack 是一项商业服务,提供在通用操作系统上运行多种浏览器的虚拟机。我已经开始使用这项服务,因为它比维护我自己的测试环境更简单。这并不是一个完美的解决方案——例如,移动浏览器是模拟器,而不是实际的硬件,但这项服务快速、易于使用,而且相当全面。您可以在http://browserstack.com 获得一个试用账户,也有提供类似功能的竞争服务。

`image 注意除了作为普通用户,我与 BrowserStack 没有任何关系——我像其他人一样为我的账户付费,我没有得到任何特殊待遇或折扣。

摘要

在这一章中,我解释了如何获得 jQuery Mobile 并将其添加到 HTML 文档中,并阐述了 jQuery Mobile 自动增强 HTML 文档并从这些文档中分离页面的基本方法。我描述了 jQuery Mobile 提供的定制事件,它使创建触摸应用变得更加容易,我还列出了一些关于如何进行移动开发和测试的基本指南。`*

二十八、页面、主题和布局

在这一章中,我描述了 jQuery Mobile 应用的一个关键构件:页面。我在第二十七章中提到了页面,但现在我将深入细节并展示如何定义、配置和在页面间导航。我还将向您展示两个有用的 jQuery Mobile 特性,用于样式化和结构化页面中的内容:主题和网格布局。表 28-1 对本章进行了总结。

表 28-1 。章节总结

问题 解决办法 列表
定义一个 jQuery Mobile 页面。 data-role属性应用于值为page的元素。 one
向页面添加页眉或页脚。 使用值headerfooterdata-role属性应用于元素。 Two
在文档中定义多个页面。 创建几个data-rolepage的元素。 three
在页面间导航。 创建一个a元素,其href元素是页面元素的id four
a元素指定过渡效果。 应用data-transition属性。 five
设置全局过渡效果。 defaultPageTransition设置赋值。 six
链接到另一个文档中的页面。 将文档的 URL 指定为一个a元素的href值。 7, 8
禁用单个链接的 Ajax。 data-ajax属性设置为false nine
全局禁用 Ajax。 ajaxEnable事件设置为false Ten
预取一页。 使用data-prefetch属性。 11, 12
更改当前页面。 使用changePage方法。 Thirteen
控制过渡效果的方向。 使用changePage方法的reverse设置。 Fourteen
指定显示加载对话框的延迟时间。 使用loadMsgDelay设置。 Fifteen
禁用加载对话框。 使用showLoadMsg设置。 Sixteen
确定当前页面。 使用activePage属性。 Seventeen
在后台加载页面。 使用loadPage方法。 Eighteen
响应页面加载。 使用页面加载事件。 Nineteen
响应页面转换。 使用页面转换事件。 Twenty
将样本应用于页面或元素。 使用data-theme属性,并将值设置为应该使用的样本。 21, 22
在网格中布置元素。 使用 jQuery Mobile 布局 CSS 类。 Twenty-three

了解 jQuery Mobile 页面

在第二十七章中,我向您展示了如何使用具有特定角色的元素在 HTML 文档中定义 jQuery Mobile 页面。概括一下,清单 28-1 显示了一个简单的页面。

清单 28-1 。HTML 文档中的简单 jQuery Mobile 页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div data-role="page">
       <div data-role="content">
           This is Jacqui's Flower Shop
       </div>
   </div>
</body>
</html>

这是一个最小的页面,由两个关键元素组成,每个元素都有一个data-role 属性。角色为page的元素表示包含 jQuery Mobile 页面的 HTML 内容区域。正如我在第二十七章中提到的,jQuery Mobile 的一个关键特性是显示给用户的页面与包含它们的 HTML 元素没有直接关系。

另一个重要的元素有content 的作用。这表示 jQuery Mobile 页面中包含页面内容的部分。一个页面可以包含不同的部分,其中的内容只有一个,我将很快演示。您可以在图 28-1 中看到清单中的 HTML 是如何在浏览器中显示的。

9781430263883_Fig28-01.jpg

图 28-1 。在浏览器中显示最小的 jQuery Mobile 页面

向页面添加页眉和页脚和

除了内容部分,jQuery Mobile 页面还可以包含页眉和页脚,由元素表示,这些元素的data-role属性分别设置为headerfooter。清单 28-2 展示了添加到示例页面中的这两个部分。

清单 28-2 。向示例页面添加页眉和页脚

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
        </div>
        <div data-role="footer">
           <h1>Home Page</h1>
        </div>
   </div>
</body>
</html>

你可以在图 28-2 中看到这些添加的效果。

image 注意页眉页脚在小屏幕上可以占据很大空间,如图所示。

9781430263883_Fig28-02.jpg

图 28-2 。向页面添加页眉和页脚

image 提示注意,页脚显示在内容部分的末尾,而不是页面的底部。您可以通过将data-position属性设置为fixed来固定页眉和页脚的位置——这样可以保持页眉和/或页脚的位置,同时允许其余内容自由滚动。使用该选项时要彻底测试:不是所有的浏览器都支持固定页眉和页脚所需的 CSS 特性。

向文档添加页面

您可以在一个文档中定义多个 jQuery Mobile 页面。这对于简单的 web 应用非常有用,因为您可以将需要的所有内容打包到一个 HTML 文件中,这可以减少必须向服务器发出的请求数量和必须传输的数据总量(因为有些元素——如head部分中的元素——对于多个页面只指定一次)。清单 28-3 显示了一个多页文档。

清单 28-3 。在 HTML 文档中定义多个 jQuery Mobile 页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <divid="page1"data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
        </div>
   </div>
</body>
</html>

本示例定义了文档中的两个页面。我使用了id属性给每个页面分配一个唯一的标识符,这些值构成了页面间导航的基础。加载 HTML 文档时,只显示第一页。为了让用户在页面间导航,我添加了一个a元素,它的href是目标页面的id,如清单 28-4 所示。

清单 28-4 。在页面间导航

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
           <p><a href="#page2">Go to page 2</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

在这个例子中,我在页面之间添加了链接。当一个链接被点击时,jQuery Mobile 会显示文档中相应的页面,如图 28-3 所示。

9781430263883_Fig28-03.jpg

图 28-3 。在文档的页面间导航

配置页面过渡

当用户在页面之间导航时,jQuery Mobile 使用动画效果在一个页面和下一个页面之间切换。默认的效果叫做slide ,即将出页滑向左边,而新页从右边滑入。jQuery Mobile 定义了许多不同的效果,如下所示:

  • slide
  • pop
  • slideup
  • slidedown
  • slidefade
  • fade
  • flip
  • turn
  • flow
  • none(意为无效果,也表示为null)

并非所有移动设备都正确支持所有转换,您可能会遇到闪烁和断断续续的情况。jQuery Mobile 的每个新版本都增加了可以支持所有转换的设备数量,但是您应该始终进行彻底的测试,以确保您在目标设备上看不到任何问题。如果有疑问,试试fadeslide过渡,我发现它们在最少的设备上有问题。

image 提示移动浏览器模拟器不能很好地处理过渡,通常会忽略它们。然而,它们在真实的移动设备上运行良好。如果你想在桌面上看到过渡,那么使用谷歌 Chrome 或苹果 Safari,这两种浏览器都能很好地处理效果。

通过使用a元素上的data-transition 属性,将值设置为您想要的效果,您可以更改单个页面过渡的动画方式。清单 28-5 提供了一个例子。

清单 28-5 。使用数据转换 属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
           <p><a href="#page2" data-transition="turn">Go to page 2</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

image 注意我不能轻易用数字给你展示不同的动画效果。这个例子需要在浏览器中进行实验。你可以通过下载本书附带的源代码来避免输入 HTML,这些源代码可以从Apress.com免费获得。

当用户点击突出显示的链接时,turn转换用于显示目标页面。turn效果仅应用于该单个链接。页面中的其他链接或同一文档中的其他页面将继续使用默认链接。如果要禁用动画效果,请将data-transition属性设置为none

image 提示你可以通过将data-direction属性应用到值为reversea元素来改变效果播放的方向。在“改变当前页面”一节中,我给出了一个反转过渡方向的例子,并解释了它为什么有用。

如果你想改变用于所有导航的动画效果,那么你需要设置一个全局选项。jQuery Mobile 定义了defaultPageTransition 设置,可以在mobileinit事件被触发时进行设置。清单 28-6 展示了这是如何做到的。

清单 28-6 。更改默认页面过渡

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("mobileinit", function() {
            $.mobile.defaultPageTransition = "fade";
        })
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
           <p><a href="#page2">Go to page 2</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

没有方便的方法为mobileinit事件注册一个处理函数,所以你必须选择document对象并使用bind方法。该方法的参数是您要处理的事件的名称以及事件被触发时要使用的处理函数。

image 注意jQuery Mobile 脚本库一加载就触发mobileinit事件,这意味着在script元素中引用 jQuery Mobile 脚本库之前,您必须注册处理函数来更改全局 jQuery Mobile 设置。您可以在清单中看到我是如何做到的。如果在加载 jQuery Mobile 代码的script元素之前没有定义对bind方法的调用,那么这个函数将永远不会被执行。

要更改全局设置的值,您需要为$.mobile对象的属性分配一个新值。因为我想改变defaultPageTransition的设置,我给$.mobile.defaultPageTransition属性赋值,如下所示:

...
$.mobile.defaultPageTransition = "fade";
...

该语句将默认效果设置为fade。我仍然可以用data-transition属性覆盖这个设置。

链接到外部页面

您不必在一个文档中包含所有页面。您可以像使用常规 HTML 一样添加链接。为了演示这一点,我创建了一个名为document2.html 的新文件,其内容如清单 28-7 所示。

清单 28-7 。document2.html 文件的内容

<!DOCTYPE html>
<html>
<head>
    <title>Document 2</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 1 in document2.html
           <p><a href="#page2">Go to page 2 in this document</a></p>
           <p><a href="example.html">Return to example.html</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2 in document2.html
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

该文档包含一对 jQuery Mobile 页面,遵循与其他示例相同的结构。链接到其他文档中的页面很简单。您只需定义一个a元素,其href属性包含目标文档的 URL,如清单 28-8 所示。

清单 28-8 。导航到另一个 HTML 文档中的页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
           <p><a href="#page2">Go to page 2</a></p>
           <p><a href="document2.html">Go to document2.html</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

jQuery Mobile 使用 Ajax 加载指定的文档,并自动显示第一页,如果指定了过渡效果,还会使用过渡效果。你可以在图 28-4 中看到结果。

9781430263883_Fig28-04.jpg

图 28-4 。导航到另一个文档中的页面

image 提示 jQuery Mobile 自动将其样式和增强应用于通过 Ajax 加载的远程文档。这意味着您不必将 jQuery 和 jQuery Mobile 的scriptlink元素包含在文件中,例如我在示例中使用的document2.html文件。也就是说,我建议您包含这些引用,因为这可能会阻止 jQuery Mobile 在发出此类请求时使用 Ajax,如果这样做了,那么内容的自动处理就不会执行。

处理 Ajax/页面 ID 问题

当链接到其他文档的页面时,并不是一帆风顺的。Ajax 内容的管理方式和 jQuery Mobile 页面的定义方式之间存在冲突。两者都依赖于元素的id属性的值。图 28-5 显示了这个问题。

9781430263883_Fig28-05.jpg

图 28-5 。多页面 Ajax 问题

在这个图中,我点击了应该显示document2.html中的page2元素的链接,但我得到的实际上是example.html中的page2元素,这是一个令人困惑和意想不到的结果。

你可以用两种方法来解决这个问题。首先是每个 HTML 文档只定义一个 jQuery Mobile 页面——这是 jQuery Mobile 团队的建议。

第二种方法是在加载多页文档时禁用 Ajax。这解决了这个问题,但是这意味着 jQuery Mobile 在显示新页面时无法应用过渡效果。您可以通过将data-ajax属性设置为false来禁用单个a元素的 Ajax,如清单 28-9 所示。

清单 28-9 。禁用单个链接的 Ajax

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
           <p><a href="#page2">Go to page 2</a></p>
           <p><a href="document2.html"data-ajax="false">Go to document2.html</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

在这个例子中,我为导航到document2.html 的链接禁用了 Ajax。如图图 28-6 所示,这产生了预期的导航序列。

9781430263883_Fig28-06.jpg

图 28-6 。禁用 Ajax 以避免元素 id 冲突

默认情况下,您可以使用ajaxEnabled全局设置关闭 Ajax,这在清单 28-10 中有演示。当该设置为false时,除非将data-ajax属性应用于值为true的元素,否则 Ajax 不会用于导航。

清单 28-10 。使用全局设置禁用 Ajax

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("mobileinit", function() {
            $.mobile.ajaxEnable = false
        })
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
           <p><a href="#page2">Go to page 2</a></p>
           <p><a href="document2.html">Go to document2.html</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

预取页面

您可以让 jQuery Mobile 预取文档,这样当用户单击一个链接时,它们包含的页面就可以立即使用。这样做的好处是您创建了一个响应速度更快的应用,但是您是通过下载用户可能不需要的内容来做到这一点的。为了演示这个特性,我创建了一个名为singlepage.html 的文档,其内容如清单 28-11 所示。

清单 28-11 。singlepage.html 档案

<!DOCTYPE html>
<html>
<head>
    <title>Single Page</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is the only page in this document
           <p><a href="example.html">Return to example.html</a></p>
        </div>
    </div>
</body>
</html>

决定是否预取内容

预取内容是一个困难的决定。从应用的角度来看,预取是一个好主意,因为当用户在页面之间导航时,预取会产生即时响应。当移动连接速度慢、覆盖不稳定时,这一点尤为重要。用户不喜欢等待,如果内容不可用,持续掉线的连接将使您的应用不可用。

另一方面,你冒着下载内容的风险,因为你预料到用户可能不会进行导航操作。当移动数据计划对下载数据收取惩罚性费用并且每月带宽限制较低时,这可能是不受欢迎的。通过预取内容,您假设用户认为您的应用足够重要,可以用带宽(和成本)来换取性能,但事实可能并非如此。令人难过的事实是,尽管你可能在过去的一年里一直生活和呼吸着你的项目,但它对你的用户来说可能只不过是一种温和的便利。

我的建议是而不是预取页面。对于那些认为你的应用足够重要的用户,你可以给他们一个选项来启用预取。

您可以通过将data-prefetch属性应用到a元素并将其设置为true来启用预取。清单 28-12 显示了应用于example.html文档的data-prefetch属性。

清单 28-12 。预取内容

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is Jacqui's Flower Shop
           <p><a href="#page2">Go to page 2</a></p>
           <p>
               <a href="singlepage.html" data-prefetch="true">Go to singlepage.html</a>
           </p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

在这个例子中,我让 jQuery Mobile 预取我的 URL 的目标。当我点击链接时,jQuery Mobile 能够导航到预取的内容,避免任何延迟。

image 提示当你在一个快速、可靠的网络上进行开发时,很难确定预取等功能是否有效。我喜欢通过使用调试 HTTP 代理来检查这些特性,它向我显示了从浏览器发送的请求。如果你是 Windows 用户,那么我推荐 Fiddler,这是一个优秀的工具,可以无限配置和定制。提琴手可以从www.fiddler2.com下载。

使用脚本控制 jQuery Mobile 页面

你不需要总是依赖用户点击链接来管理页面导航。幸运的是,jQuery Mobile 为您提供了允许您使用 JavaScript 控制导航的方法和设置。在接下来的小节中,我将向您展示如何利用这些方法来获得对 jQuery Mobile web 应用中导航的细粒度控制。

更改当前页面

changePage方法 允许您更改 jQuery Mobile 显示的页面。清单 28-13 展示了这个方法的基本用法,当点击一个按钮时,它改变显示的页面。

清单 28-13 。更改 jQuery Mobile 显示的页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function() {
           $("button").bind("tap", function(e) {
                var target = this.id == "local" ? "#page2" : "document2.html";
                $.mobile.changePage(target)
           })
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <fieldset class="ui-grid-a">
                <div class="ui-block-a"><button id="local">Local</button></div>
                <div class="ui-block-b"><button id="remote">Remote</button></div>
            </fieldset>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
           This is page 2
           <p><a href="#page1">Go to page 1</a></p>
        </div>
   </div>
</body>
</html>

在这个例子中,我添加了两个按钮,当单击时会调用changePage方法。我使用bind方法监听的事件是tap,这是 jQuery Mobile 定义的一小组有用的自定义事件之一。当用户点击屏幕(或在非触摸设备上点击鼠标)时,触发此事件。我在第二十七章的中描述了这个事件,以及其余的 jQuery Mobile 事件。

按钮是标准的 HTML 元素,jQuery Mobile 会自动将其转换成按钮小部件。我在第三十章中描述了配置 jQuery Mobile 按钮的选项。最后,你会注意到我已经给类ui-grid-aui-block-aui-block-b分配了一些元素。这些是 jQuery Mobile 对创建页面布局的支持的一部分,我将在本章稍后描述。这个例子的结果非常简单,如图 28-7 所示。当用户单击其中一个按钮时,调用changePage方法,传递本地页面的id值或另一个文档的 URL 供 jQuery Mobile 显示。内容被加载,并显示过渡效果,就像我使用常规链接时一样。

9781430263883_Fig28-07.jpg

图 28-7 。使用 changePage 方法

这是changePage方法的基本用法,但是还有一些配置选项。为了配置页面转换,您将一个 settings 对象作为第二个参数传递给changePage方法,为一个或多个设置指定值。表 28-2 描述了可用的设置。这些设置中的大部分最好保留默认值,但是在接下来的部分中,我将展示两个更频繁修改的设置。

表 28-2 。changePage 方法的设置

环境 描述
allowSamePageTransition 当设置为false(默认值)时,jQuery Mobile 将忽略目标页面为当前页面的changePage请求。值true允许这样的请求,尽管这会导致一些过渡动画的问题。
changeHash true时,URL 栏中的哈希片段将被更新到新的位置(这样页面标识符就包含在 URL 中了)。默认为true
data 指定用于加载文档的 Ajax 请求中包含的数据。
dataUrl 指定更新浏览器 URL 栏时使用的 URL。缺省值是 no value,这意味着该值取自内部页面的id或远程文档的 URL。
loadMsgDelay 指定向用户显示加载图像的毫秒数。默认为50
pageContainer 指定应包含新页面的元素。
reloadPage true时,jQuery Mobile 将重新加载远程文档的内容,即使数据已经被缓存。默认为false
reverse true时,过渡效果将向后播放。默认为false
role 设置新内容的data-role值——你可以在第二十九章的中看到这个设置的使用。
showLoadMsg 当加载远程文件时,true值将显示加载图像。默认为true
transition 指定显示新页面时要使用的过渡效果。
type 指定用于请求文档的 HTTP 方法。允许的值是getpost。默认是get

image 提示这些选项也被loadPage方法所使用,我将在本章稍后描述。

控制过渡效果的方向

reverse设定是我最常用的一个。jQuery Mobile 总是以相同的方式播放过渡效果,当您向用户呈现一个动作,有效地将他们送回之前的页面,或者您正在响应 jQuery Mobile swiperight事件时,这并不总是有意义的。清单 28-14 展示了如何解决这个问题。

清单 28-14 。过渡效果方向与导航意图不匹配

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function() {
           $.mobile.defaultPageTransition = "slide";
           $("button").bind("tap", function(e) {
                var target = this.id == "forward" ? "#page2" : "#page1";
                $.mobile.changePage(target, {
                    reverse: (target == "#page1")
                });
           })
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is page 1
            <button id="forward">Go to Page 2</button>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is page 2
            <button id="back">Back to Page 1</button>
        </div>
   </div>
</body>
</html>

该文档有两个页面,每个页面都包含一个导航到另一个页面的按钮。第二页上的按钮标记为返回第一页。当点击第二页上的按钮时,我通过使用一个truereverse值来改变过渡效果的方向。我无法在静态页面上显示这种效果,但这种效果感觉要自然得多。有一些潜意识的期望形成了,基于导航提示,动画应该播放,你是返回到上一页,而不是前进到新的一页。如果你在浏览器中查看这个例子,你就会明白我的意思。

控制负载动画

当 jQuery Mobile 通过 Ajax 加载远程文档超过 50 毫秒时,它会显示一个动画图像。当使用移动浏览器模拟器和高速网络时,jQuery Mobile 能够快速加载文档,以至于永远不会显示对话框。但是如果你使用一个实际的移动数据网络,或者像我所做的那样,在请求中引入一个延迟,那么这个对话框会在屏幕上停留足够长的时间来被看到,如图 28-8 所示。

9781430263883_Fig28-08.jpg

图 28-8 。“jQuery Mobile 加载”对话框

您可以通过为loadMsgDelay设置提供一个值来改变对话框显示的时间间隔,如清单 28-15 所示。

清单 28-15 。更改加载对话框的延迟

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">

        $(document).bind("mobileinit", function() {
            $.mobile.loadingMessage = "Loading Data..."
        })

        $(document).bind("pageinit", function() {
           $("button").bind("tap", function(e) {
                $.mobile.changePage("document2.html",{
                    loadMsgDelay: 1000
                });
           })
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button id="forward">Go</button>
        </div>
    </div>
</body>
</html>

在这个例子中,我已经指定 jQuery Mobile 在向用户显示加载对话框之前应该等待一秒钟。

image 提示您可以通过为全局loadingMessage属性设置一个新值来更改加载对话框中的文本,如示例所示。与所有 jQuery Mobile 全局属性一样,这应该在触发mobileinit事件时执行的函数中设置。

当您调用changePage方法时,您可以通过为showLoadMsg设置指定false来禁用此对话框。我不建议这么做,因为给用户提供反馈总是一件好事,但是清单 28-16 显示了使用中的设置。

清单 28-16 。禁用加载对话框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function() {
           $("button").bind("tap", function(e) {
                $.mobile.changePage("document2.html", {
                    showLoadMsg: false
                });
           })
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button id="forward">Go</button>
        </div>
    </div>
</body>
</html>

确定当前页面

您可以使用$.mobile.activePage属性来确定 jQuery Mobile 正在显示的当前页面。清单 28-17 展示了activePage属性的使用。

清单 28-17 。使用 activatePage 属性

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">

        var eventHandlerCreated = false;

        $(document).bind("pageinit", function () {
            if (!eventHandlerCreated) {
                $("button").bind("tap", function (e) {
                    var nextPages = {
                        page1: "#page2",
                        page2: "#page3",
                        page3: "#page1"
                    }
                    var currentPageId = $.mobile.activePage.attr("id");
                    $.mobile.changePage(nextPages[currentPageId]);
                })
                eventHandlerCreated = true;
            }
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is page 1
            <button id="forward">Go</button>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is page 2
            <button id="Button1">Go</button>
        </div>
    </div>
    <div id="page3" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is page 3
            <button id="Button2">Go</button>
        </div>
    </div>
</body>
</html>

本例中有三个页面,每个页面都有一个按钮。当按钮被点击时,我读取activePage属性来获取当前页面。activePage属性返回一个包含当前页面的 jQuery 对象,所以我使用 jQuery attr方法来获取id属性的值。

我的脚本包括一个简单的映射,它告诉我文档中每个页面的下一页应该是什么,我使用从activePage属性获得的id值作为changePage方法的参数,确保我按照映射定义的顺序浏览页面。

image 提示注意,我使用了一个名为eventHandlerCreated的变量来确保我只为tap事件创建一个处理函数。changePage方法触发pageinit事件,这会导致设置多个处理函数——这是 jQuery Mobile 应用中的一个常见问题,如果单击一个按钮会导致多个页面转换,这也是可能的原因。防止这个问题的最可靠的方法是使用一个像例子中这样的变量。

在后台加载页面

您可以使用loadPage方法 加载远程文档,而不向用户显示它们。这相当于我在本章前面演示的预取。loadPage方法有两个参数。第一个是要加载的文档的 URL,第二个是可选的设置对象。loadPage方法支持设置为changePage方法,我在表 28-2 中描述过。清单 28-18 显示了正在使用的loadPage方法。

清单 28-18 。使用 loadPage 方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">

        var loadedPages = false;

        $(document).bind("pageinit", function () {
            if (!loadedPages) {
                $.mobile.loadPage("document2.html", {}).done(function () {
                    $("#gobutton").button("enable").bind("tap", function () {
                        $.mobile.changePage("document2.html");
                        loadedPages = true;
                    })
                })
            }
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button id="gobutton" disabled="disabled">Go</button>
        </div>
    </div>
</body>
</html>

image 提示注意,我传递了一个空的设置对象({})作为loadPage方法的第二个参数。jQuery 1 . 3 . 1 版中有一个缺陷,即使没有更改设置,也需要设置对象。

在这个例子中,我使用了loadPage方法来预加载document2.html文件。loadPage方法返回一个延迟对象,当页面加载后,您可以用它来接收通知。我在第三十五章的中解释了延迟对象,但是现在只要知道你可以在由loadPage方法返回的对象上调用done方法,指定一个当由loadPage启动的 Ajax 请求完成时将被执行的函数就足够了。

在这个例子中,我使用 jQuery UI 按钮小部件上的enable方法来启用页面中的按钮,并为tap事件注册一个句柄。当按钮被单击时,我调用changePage方法导航到预取的文档。(我在第三十章中描述了 jQuery Mobile 对按钮的支持。)

注意,我定义了一个名为loadedPages的变量。这解决了我在使用changePage方法(内部调用loadPage)时遇到的相同问题:每当 jQuery Mobile 初始化页面时,就会触发pageinit事件。这意味着当例子中的文档被加载时,事件被触发,当loadPage通过 Ajax 加载document2.html时,事件再次被触发。我使用loadedPages变量来确保我只尝试预加载一次内容。除非我启用了reload设置,否则调用loadPage两次(如果没有loadedPages变量就会发生这种情况)不会是世界末日。这将导致文档的缓存副本被忽略,并传输document.html两次。我将在下一节解释 jQuery Mobile 页面事件集。

使用页面事件

jQuery Mobile 定义了一组事件,您可以使用这些事件来接收关于页面变化的通知。这些事件在表 28-3 中有描述,我将在接下来的章节中展示一些更有用的事件。

表 28-3 。jQuery Mobile 页面事件

事件 描述
pagebeforecreate 初始化页面时触发。
pagecreate 当页面已经创建但大多数自动增强尚未执行时触发。
pageinit 页面初始化后触发。
pageremove 在移除页面之前触发。
pagebeforeload 在通过 Ajax 请求页面之前触发。
pageload 通过 Ajax 成功加载页面时触发。
pageloadfailed 当页面无法通过 Ajax 加载时触发。
pagebeforechange 在执行页面转换之前触发。
pagechange 在执行页面转换后触发。
pagechangefailed 当页面更改失败时触发(这通常是因为无法加载请求的文档)。
pagebeforeshow 在向用户显示页面之前触发。
pagebeforehide 在页面从显示中移除之前触发。
pageshow 向用户显示页面后触发。
pagehide 页面对用户隐藏后触发。

处理页面初始化事件

事件是最有用的 jQuery Mobile 页面事件,如第二十七章中的所述。当您想要使用脚本配置页面时,您需要响应的就是这个事件。我不打算再次演示这个事件,因为到目前为止您已经在每个示例中看到了它,但是我要强调的是,在使用 jQuery Mobile 时,使用标准的 jQuery $(document).ready()方法是不可靠的。

处理页面加载事件

pagebeforeloadpageloadpageloadfailed事件对于监控远程页面的 Ajax 请求非常有用,这些请求可以由 jQuery Mobile 自动生成,也可以通过changePageloadPage方法以编程方式生成。在演示loadPage方法时,我使用了一个延迟对象来响应页面加载,但是您可以使用pageload方法(当然,还有出错时的pageloadfailed方法)获得相同的结果。清单 28-19 显示了更新后使用pageload事件的loadPage示例。

清单 28-19 。使用 pageload 事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">

        $(document).bind("pageload", function(event, data) {
            if (data.url == "document2.html") {
                $("#gobutton").button("enable").bind("tap", function() {
                    $.mobile.changePage("document2.html");
                })
            }
        })

        var loadedPages = false;
        $(document).bind("pageinit", function() {
            if (!loadedPages) {
                loadedPages = true;
                $.mobile.loadPage("document2.html", {});
            }
        });

    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button id="gobutton" disabled>Go</button>
        </div>
    </div>
</body>
</html>

在这个例子中,我指定了一个函数,当pageload事件被触发时将执行这个函数。jQuery Mobile 通过将数据对象作为第二个参数传递给函数来提供关于请求的信息。我使用url属性来检查已经加载的文档是否是我所期望的。数据对象为pageload事件定义的属性集在表 28-4 中描述。从表中可以看到,大部分属性都对应于 jQuery Ajax 支持,我在第 14 和 15 章中描述过。

表 28-4 。页面加载数据对象事件属性

财产 描述
url 返回传递给loadPage方法的 URL(这是 jQuery Mobile 在请求页面时使用的方法,也是changePage方法使用的方法)。
absUrl 请求的绝对 URL。
options Ajax 请求选项。关于配置 Ajax 的详细信息,请参见第十四章和第十五章。
xhr 用于请求的 jQuery Ajax 请求对象。参见第十四章和第十五章了解该物体的详细信息。
textStatus 请求状态的字符串描述。详见第十四章和第十五章。

响应页面转换

您可以使用页面转换事件在用户从一个页面导航到另一个页面时得到通知(或者当您使用changePage方法以编程方式这样做时)。这些事件(pagebeforehidepagehidepagebeforeshowpageshow)在每次页面转换时都会被触发,即使该页面之前已经向用户显示过。清单 28-20 显示了其中一个事件的使用。

清单 28-20 。响应被隐藏的页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">

        var registeredHandlers = false;
        $(document).bind("pageinit", function() {
            if (!registeredHandlers) {
                registeredHandlers = true;
                $("#page1").bind("pagehide", function(event, data) {
                    $.mobile.changePage($("#page1"));
                })
            }
        });

    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="document2.html">Go to document2.html</a>
        </div>
    </div>
</body>
</html>

在这个例子中,我通过选择我想要的元素并调用bind方法,为page1元素上的pagehide事件注册了一个处理函数。这意味着只有当选定的页面被隐藏时,我才会收到事件。这是一个相当愚蠢的例子,因为每当pagehide事件被触发时,它只是使用changePage方法返回到page1,但是它确实演示了事件的用法。请注意,我仍然使用变量来确保只注册一次处理函数。如果我没有这样做,那么当document2.html页面被加载时,两个函数将在同一个元素上为同一个事件注册。

应用 jQuery Mobile 主题

jQuery Mobile 提供了对主题的支持,更像是 jQuery UI 提供的主题支持的简化版本。您在第二十七章中下载并安装的 jQuery Mobile 文件中包含一个默认主题,您可以使用 jQuery Mobile ThemeRoller 应用创建自定义主题,该应用是您用于 jQuery UI 的同名应用的变体。

image 注意正如我在第二十七章中提到的,我不会在这本书的这一部分创建和使用自定义主题。jQuery Mobile ThemeRoller 应用没有预先构建的主题集合,并且很难用文字描述创建主题的过程。在这一章中我将使用默认主题,但是你可以在http://jquerymobile.com/themeroller看到 jQuery Mobile ThemeRoller。

jQuery Mobile 主题由一个或多个样本组成,这些样本是一组应用于不同种类元素的样式。样本由一个字母识别,以 A 开始。默认主题有五个样本,命名为AE

要在 ThemeRoller 中查看默认主题,请导航至http://jquerymobile.com/themeroller;点击Import链接;并点击Import Default Theme链接,将jquery.mobile-1.3.1.css文件的内容导入对话框(这是你在第二十七章下载安装的 CSS 文件)。单击Import按钮,ThemeRoller 将处理 CSS 并显示默认色板,如图图 28-9 所示。

9781430263883_Fig28-09.jpg

图 28-9 。默认主题中的样本

通过使用data-theme属性将主题应用到 jQuery Mobile 页面,将值设置为所需样本的名称。清单 28-21 提供了一个例子。

清单 28-21 。在主题中使用样本

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="a">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is Theme A
            <a href="#page2" data-role="button">Switch Theme</a>
        </div>
    </div>
    <div id="page2" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is Theme B
            <a href="#page1" data-role="button">Switch Theme</a>
        </div>
    </div>
</body>
</html>

我已经将data-theme属性应用到了page元素,其效果是将指定的样本应用到页面中的所有子元素。在这个例子中有两个页面,这样我就可以展示相同的内容是如何通过一对对比主题显示的——并指出在 jQuery Mobile 中,一旦页面通过了自动增强过程,就没有可靠的方法来更改主题。data-theme属性被转换成一组 CSS 类,这些类在页面初始化过程中应用于元素,因此改变属性值不会改变类。你可以在图 28-10 中看到两页的例子。

9781430263883_Fig28-10.jpg

图 28-10 。同一应用中使用不同色板的两个页面

image 提示还有一个叫active的色板,用来高亮显示选中的按钮。这是由 jQuery Mobile 自动应用的,但是您也可以直接使用它。如果你这样做了,确保你没有通过创建冲突的活动元素来混淆用户。

将样本应用到单个元素

上一个示例展示了如何设计整个页面的样式,但是您也可以在每个元素的基础上应用色板,混合和匹配以获得特定的效果。清单 28-22 提供了一个演示。

清单 28-22 。将色板应用到单个元素

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page"data-theme="a">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="document2.html" data-role="button"data-theme="b">Press Me</a>
            <a href="document2.html" data-role="button"data-theme="c">Press Me</a>
            <a href="document2.html" data-role="button"data-theme="e">Press Me</a>
        </div>
    </div>
</body>
</html>

在这个例子中,我将data-theme应用于页面和三个button元素,每个元素指定一个不同的样本。你可以在图 28-11 中看到效果。

9781430263883_Fig28-11.jpg

图 28-11 。将不同的色板应用于同一页面中的元素

jQuery Mobile 对主题的支持简单易用。如果您能够动态地更改元素使用的样本,那就太好了,但是即使忽略了这一点,您也可以使用样本来定制移动应用的外观。

创建网格布局

jQuery Mobile 定义了一些有用的 CSS 类,您可以使用它们以网格的形式展示移动页面的内容。这是您自己可以做的事情,但是将它们内置到库中是有用的,并且减少了所需的定制开发量,尤其是对于简单的移动应用。jQuery Mobile 定义了四个网格类,每个类包含不同数量的列,如表 28-5 所示。

表 28-5 。jQuery Mobile 布局网格

CSS 类
ui-grid-a 2
ui-grid-b 3
ui-grid-c 4
ui-grid-d 5

将一个网格类应用于容器元素,然后将类应用于其中的每个内容项,从每列的ui-block-aui-block-b开始,依此类推。清单 28-23 展示了使用这些类来创建一个简单的网格。

清单 28-23 。使用 jQuery Mobile 布局类创建网格

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <divclass="ui-grid-b">
                <divclass="ui-block-a"><button>Press Me</button></div>
                <divclass="ui-block-b"><button>Press Me</button></div>
                <divclass="ui-block-c"><button>Press Me</button></div>
            </div>
            <divclass="ui-grid-a">
                <divclass="ui-block-a"><button>Press Me</button></div>
                <divclass="ui-block-b"><button>Press Me</button></div>
            </div>
            <div><button>Press Me</button></div>
        </div>
    </div>
</body>
</html>

在这个例子中,我创建了两个网格:一个有三列,另一个有两列。每一列包含一个button元素,我在网格外添加了一个按钮来强调默认行为,这是为了让元素跨越屏幕。你可以在图 28-12 中看到结果。

9781430263883_Fig28-12.jpg

图 28-12 。在网格中布局元素

摘要

在这一章中,我描述了页面,它是 jQuery Mobile 的关键构件之一。我还描述了 jQuery Mobile 对主题和网格布局的支持。这些特性为最重要的 jQuery Mobile 特性提供了基础:小部件。在第二十九章中,我从描述对话框和弹出窗口部件开始。

二十九、对话框和弹出窗口小部件

在这一章中,我将介绍前两个 jQuery Mobile 小部件:对话框和弹出窗口。jQuery Mobile 小部件的行为与 jQuery UI 中的小部件略有不同,但是——您将会了解到——有一个共同的方法来支持这两个库。表 29-1 对本章进行了总结。

表 29-1 。章节总结

问题 解决办法 列表
创建一个对话框小部件。 将值为 dialog 的data-role属性添加到包含对话框内容的div元素中,或将值为dialogdata-rel属性添加到将打开对话框的导航链接中。 1, 2
以编程方式创建对话框。 使用changePage方法。 three
向对话框添加按钮。 a元素添加到对话框元素中。 4, 5
配置对话框。 使用配置数据属性或调用 dialog 方法并使用配置选项。 6, 7
关闭对话框。 调用close方法。 eight
创建一个弹出窗口小部件。 将值为popupdata-role属性添加到将显示小部件的 jQuery Mobile 页面中的div元素中。 nine
配置弹出窗口。 将数据属性应用于打开弹出窗口的a元素,或者包含弹出窗口的div元素,或者调用popup方法。 10–12
以编程方式控制弹出窗口。 使用openclose,reposition方法。 Thirteen
响应弹出窗口中的更改。 处理弹出事件。 Fourteen

使用 jQuery Mobile 对话窗口小部件

顾名思义,对话框小部件为用户提供了一个对话框。因为这是我描述的第一个 jQuery Mobile 小部件,所以我将向您展示创建和管理小部件的不同方法,为本章的剩余部分和后面的章节做准备。

创建对话框小部件

当 jQuery Mobile 遇到一个元素的data-role属性被设置为dialog时,它会自动创建对话框小部件,如清单 29-1 所示。

清单 29-1 。使用数据角色属性以声明方式创建对话框小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#dialog1">Show the dialog</a>
        </div>
    </div>
    <div id="dialog1" data-role="dialog">
        <div data-role="header">
           <h1>You clicked the link!</h1>
        </div>
        <div data-role="content">
            This is the content area of the dialog
        </div>
    </div>
</body>
</html>

这个例子包含一个带有a元素的页面,当点击这个页面时,它将导航到名为dialog1的元素。dialog1元素的data-role属性被设置为dialog,这使得 jQuery Mobile 将元素及其内容显示为对话框,如图 29-1 中的所示。

9781430263883_Fig29-01.jpg

图 29-1 。将页面显示为对话框

也可以通过将data-rel属性应用于导航a元素来创建对话框。在清单 29-2 中,您可以看到我如何将data-rel属性设置为dialog,以便从一个data-role属性设置为page的元素创建一个对话框小部件的实例。

清单 29-2 。使用 data-rel 属性从页面创建对话框小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#page2" data-rel="dialog">Show the dialog</a>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>You clicked the link!</h1>
        </div>
        <div data-role="content">
            This is the content area of the dialog
        </div>
    </div>
</body>
</html>

在这个例子中,有两个常规的 jQuery Mobile 页面。第一页中的链接就像我在第二十八章中用于导航的链接一样,除了我已经应用了data-rel属性并将值设置为dialog

以编程方式创建对话框小部件

声明性方法是有用的,但是它要求您确定内容在 HTML 中静态显示的方式。有时你会想要决定动态地将一个页面显示为一个对话框,这可以通过changePage方法和它支持的role设置来完成。清单 29-3 展示了使用changePage方法来创建一个对话框。

清单 29-3 。使用 changePage 方法以编程方式创建对话框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function (event, data) {
            $("#dialogLink").click(function (e) {
                $.mobile.changePage(this.href, {
                    role: "dialog"
                });
            });
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <p><a id="dialogLink" href="#page2">Show the dialog</a></p>
            <p><a href="#page2">Show the page</a></p>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>You clicked the link!</h1>
        </div>
        <div data-role="content">
            This is page 2
            <a href="#" data-role="button" data-rel="back">Close</a>
        </div>
    </div>
</body>
</html>

这种技术意味着你可以用不同的方式使用相同的内容。在清单中,我定义了两个链接,其中一个链接允许使用标准导航来加载在同一个 HTML 文档中定义的页面。我已经为来自另一个a元素的click事件定义了一个处理函数,我通过调用设置为dialogrole方法来处理它。这意味着页面的处理方式将取决于点击了哪个链接,如图 29-2 所示。

9781430263883_Fig29-02.jpg

图 29-2 。以编程方式创建对话框

image 注意使用这种技术时需要小心,因为 jQuery Mobile 会缓存与内容相关联的角色——这意味着一旦您将页面显示为对话框,它将始终显示为dialog,即使您使用pagerole设置再次调用changePage(或者直到 HTML 文档被重新加载)。

向对话框添加按钮

通过向对话框的内容添加一个button元素,将data-role属性设置为 button,将data-rel属性设置为back,可以向对话框添加一个关闭按钮。我在第三十章中完整描述了 jQuery Mobile 按钮部件,但是你可以在清单 29-4 中看到它是如何应用于对话框部件的。

清单 29-4 。向对话框添加关闭按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#page2" data-rel="dialog">Show the dialog</a>
        </div>
    </div>
    <div id="page2" data-role="page">
        <div data-role="header">
           <h1>You clicked the link!</h1>
        </div>
        <div data-role="content">
            This is the content area of the dialog
            <a href="#" data-role="button" data-rel="back">Close</a>
        </div>
    </div>
</body>
</html>

我不必为链接指定目标。我只是将href属性设置为#,并让 jQuery Mobile 来决定该做什么。这很有用,因为你可能想在不同的页面上显示一个对话框,但你不知道是哪个页面导致了这个对话框的显示,也不知道浏览器应该返回到哪里。图 29-3 显示了在对话框中添加这个元素的效果。

9781430263883_Fig29-03.jpg

图 29-3 。在对话框中添加关闭按钮

您可以在对话框中添加更多的按钮,这些按钮包含更多的a元素,这些元素的href属性指向您想要显示的页面。这就是我在清单 29-5 中所做的,我在同一个 HTML 文档中添加了一个到另一个 jQuery Mobile 页面的链接。

image 提示如果你不想通过导航到一个新页面来响应一个按钮,那么你可以从a元素中处理click事件,并使用close方法来关闭对话框——参见本章后面的“使用对话框方法”一节中的例子。

清单 29-5 。向对话框添加导航按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#page2" data-rel="dialog">Show the dialog</a>
        </div>
    </div>
    <div id="page2" data-role="page" data-overlay-theme="d">
        <div data-role="header">
           <h1>You clicked the link!</h1>
        </div>
        <div data-role="content">
            This is the content area of the dialog
            <a href="#page3" data-role="button">OK</a>
            <a href="#" data-role="button" data-rel="back">Close</a>
        </div>
    </div>
    <div id="page3" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            This is page 3\. You came here via the dialog.
        </div>
    </div>
</body>
</html>

在这个例子中,我添加了一个将用户带到page3a元素,我也将它添加到了文档中。图 29-4 显示了从对话框到新页面的导航。

9781430263883_Fig29-04.jpg

图 29-4 。在对话框中添加导航链接/按钮

配置对话框小部件

对话框小部件定义了一组配置选项,既可以使用data属性以声明方式设置,也可以使用 JavaScript 方法调用来设置。我已经在表 29-2 中列出了两组选项,下面我将描述如何使用声明式和编程式配置。

表 29-2 。设置为对话框小工具

数据属性 环境 描述
data-close-btn closeBtn 获取或设置对话框标题中“关闭”按钮的位置。支持的值有leftrightnone
data-close-btn-text closeBtnText 获取或设置对话框标题中“关闭”按钮的文本。该文本不会显示给用户,但可由辅助功能软件检测到。
data-corners corners 获取或设置对话框是否应以圆角显示。默认值为true
data-overlay-theme overlayTheme 获取或设置在其上绘制对话框的主题。该设置区分大小写,必须用小写表示。

在大多数情况下,使用数据属性配置小部件并让自动增强应用您的设置会更简单、更容易。这是标准的 jQuery Mobile 配置方法,您可以看到我是如何将它用于清单 29-6 中的data-overlay-theme属性的。

清单 29-6 。用数据属性配置对话框小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#dialog1">Show the dialog</a>
        </div>
    </div>
    <div id="dialog1" data-role="dialog" data-overlay-theme="d">
        <div data-role="header">
           <h1>You clicked the link!</h1>
        </div>
        <div data-role="content">
            This is the content area of the dialog
        </div>
    </div>
</body>
</html>

我已经指定将D样本用作对话框显示的覆盖图。您可以在图 29-5 中看到效果:对话框显示在浅色背景上,而不是与默认A样本相关的黑色背景上。(我在第二十八章中描述了主题和样本的 jQuery Mobile 方法。)

9781430263883_Fig29-05.jpg

图 29-5 。用数据属性配置对话框小部件

通过调用 jQuery Mobile dialog方法,传入一个对象,该对象的属性对应于表 29-2 中您想要更改的设置,您可以在创建对话框小部件后对其进行配置。在清单 29-7 中,你可以看到我是如何使用dialog方法来配置对话框的。

清单 29-7 。以编程方式配置对话框小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("#dialog1").dialog({
                corners: false,
                overlayTheme: "e"
            });
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#dialog1">Show the dialog</a>
        </div>
    </div>
    <div id="dialog1" data-role="dialog" data-overlay-theme="d">
        <div data-role="header">
           <h1>You clicked the link!</h1>
        </div>
        <div data-role="content">
            This is the content area of the dialog
        </div>
    </div>
</body>
</html>

您可以配置小部件来响应pageinit事件,在清单中您可以看到我是如何选择小部件下面的元素并对其调用dialog方法的。我传递给dialog方法的对象具有与表 29-2 中的设置相对应的属性,你可以看到我已经为cornersoverlayTheme设置定义了新的值。传递给dialog方法的选项覆盖了data属性值,您可以在图 29-6 中看到效果。

9781430263883_Fig29-06.jpg

图 29-6 。以编程方式配置对话框小部件

image 注意jQuery Mobile API 文档建议可以使用 jQuery UI 风格的option方法调用来配置对话框小部件,像这样:$("#dialog1"), dialog("option," "corners," false)。但是,如果您使用这种方法,您将会看到一条错误消息,告诉您在小部件初始化之前不能调用方法。这个方法调用只有在小部件已经显示时才起作用,但是此时执行配置任务已经太晚了。相反,使用清单中所示的技术:将一个配置对象传递给dialog方法以响应pageinit事件。

使用对话框方法

对话框小部件只定义了一个方法,它以编程方式关闭对话框。我已经在表 29-3 中描述了该方法,以便您在将来执行快速搜索时可以更容易地找到它。

表 29-3 。对话方法

方法 描述
dialog("close") 关闭对话框。

在清单 29-8 中,我创建了一个显示固定时间的对话框。除了close方法之外,我还使用了data-close-btn属性来移除菜单栏中的关闭按钮。

清单 29-8 。使用对话框关闭方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("#page1 a").click(function (e) {
                var duration = 15;
                $("#remaining").text(duration);
                var interval = setInterval(function () {
                    $("#remaining").text(--duration);
                    if (duration == 0) {
                        clearInterval(interval);
                        $("#dialog1").dialog("close");
                    }
                }, 1000);
            });
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#dialog1">Show the dialog</a>
        </div>
    </div>
    <div id="dialog1" data-role="dialog" data-close-btn="none" data-overlay-theme="d">
        <div data-role="header">
           <h1>Important</h1>
        </div>
        <div data-role="content">
            This in an important message that will be displayed
            for <span id="remaining"></span> seconds.
        </div>
    </div>
</body>
</html>

本例中的script元素为打开对话框的链接被触发时触发的click事件设置一个处理程序。以编程方式打开对话框的唯一方法是使用changePage方法,所以我让默认事件动作负责打开对话框,并启动一个倒计时 15 秒的计时器。我每秒更新一次对话框显示的span元素的内容,并在计时器到期时调用对话框的close方法。结果是如图 29-7 所示的对话框。

9781430263883_Fig29-07.jpg

图 29-7 。一个自动关闭对话框的小工具

有几点需要注意。首先,我不需要显式刷新对话框的内容来反映span元素的变化——对话框会自动更新自己。第二点是,虽然我创建了一个没有关闭按钮的对话框,但是用户仍然可以通过使用浏览器的后退按钮来关闭我的对话框。

使用对话框事件

jQuery Mobile dialog 小部件定义了一个事件,我已经在表 29-4 中描述过了。我从不使用这个事件,而是倾向于处理我在第二十八章中描述的页面级事件。

表 29-4 。对话事件

事件 描述
create 创建对话框小部件时会触发此事件。

使用 jQuery Mobile 弹出窗口小部件

jQuery Mobile 弹出窗口小部件在弹出窗口中显示内容。弹出窗口是对话框的轻量级替代,但提供了更多的编程控制。

创建弹出窗口小部件

弹出窗口是通过将data-role属性应用到元素并将值设置为popup来创建的。弹出窗口不是自动显示的,而是当用户点击一个针对弹出窗口元素的a元素时打开的,该元素的data-rel属性也被设置为 popup。弹出窗口和打开它的链接必须在同一个 jQuery Mobile 页面中,如清单 29-9 所示。

清单 29-9 。创建 jQuery Mobile 弹出窗口小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#popup" data-rel="popup">Show the popup</a>
        </div>
        <div id="popup" data-role="popup">
            <p>This is the popup content</p>
        </div>
    </div>
</body>
</html>

点击链接会弹出一个弹出窗口,如图图 29-8 所示。在弹出窗口之外单击会将其关闭。

9781430263883_Fig29-08.jpg

图 29-8 。创建 jQuery Mobile 弹出窗口

配置弹出窗口小部件

如图所示,弹出窗口位于打开它的链接上,这很少是您想要的效果。您可以通过两种方式配置弹出窗口——或者配置打开弹出窗口的a元素,或者配置弹出窗口本身。

配置打开弹出窗口的链接

配置a元素的的优点是你可以多次使用同一个弹出窗口,但是应用不同的配置。缺点是只有两个配置选项——但是,幸运的是,它们是您可能想要调整的选项。我已经在表 29-5 中列出了可以应用于a元素的数据属性。

表 29-5 。打开弹出窗口的元素的数据属性

数据属性 描述
data-position-to 指定弹出窗口相对于打开它的a元素的位置。选项在表 29-6 中描述。
data-transition 指定用于显示弹出窗口的转换–参见第二十八章中的 jQuery Mobile 转换列表。

data-position-to属性指定弹出窗口相对于a元素的位置,可以设置为表 29-6 中所示的值。

表 29-6 。数据位置属性的值

价值 描述
origin 将弹出窗口置于a元素的中心。
window 将弹出窗口居中。
选择器 将弹出窗口置于与选择器匹配的第一个元素的中心。如果该元素不可见,则弹出窗口在窗口中居中。

在清单 29-10 中,你可以看到我是如何使用data-position-to属性通过选择器来改变弹出窗口的位置的。

清单 29-10 。通过打开弹出窗口的链接配置弹出窗口

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style>
        #anchor { position: absolute; right: 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#popup" data-rel="popup"
                data-position-to="#anchor">Show the popup</a>
            <span id="anchor">Anchor</span>
        </div>
        <div id="popup" data-role="popup">
            <p>This is the popup content</p>
        </div>
    </div>
</body>
</html>

我使用了选择器选项,为打开弹出窗口的a元素上的data-position-to属性的值指定了#anchor。这个选择器匹配一个div元素的id,我已经用 CSS 将其放置在窗口的右边缘。在图 29-9 中可以看到效果。

9781430263883_Fig29-09.jpg

图 29-9 。通过 a 元素配置弹出窗口的位置

直接配置弹出窗口

当您直接配置小部件时,有更多配置选项可用。和对话框部件一样,你可以用data属性或者通过popup方法来配置弹出窗口,我已经在表 29-7 中列出了这两种方法。大多数配置选项是不言而喻的,或者类似于dialog小部件所使用的选项。

表 29-7 。对话框小部件的设置

数据属性 环境 描述
data-corners corners 指定弹出窗口是否用圆角绘制。默认为true
data-dismissable dismissable 如果设置为false,当用户点击小工具外部时,弹出窗口不会消失。默认为true
data-history history 指定在弹出窗口打开之前是否创建历史项目。默认为true,表示点击浏览器后退按钮时浏览器关闭。
data-overlay-theme overlayTheme 指定用于覆盖的主题。默认为null,呈现透明背景。
data-position-to positionTo 使用表 29-6 中的值指定弹出窗口的位置。
data-shadow shadow 指定弹出窗口是否用阴影绘制。默认为true
data-tolerance tolerance 指定弹出窗口和窗口边缘之间的最小距离。默认值为30, 15, 30, 15
data-transition transition 指定弹出窗口打开和关闭时使用的过渡。

使用历史设置

导致弹出窗口部件最混乱的设置是data-history,它决定了在弹出窗口打开之前是否在浏览器历史中创建一个新条目。这个设置的效果意味着弹出窗口被浏览器的后退按钮关闭——但是这个特性并不总是有意义的,特别是当弹出窗口被打开和关闭以响应一个触摸事件时,如清单 29-11 所示。

清单 29-11 。弹出历史条目和鼠标触发器的效果

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <script>
        var mouseHandlerSet = false;
        $(document).bind("pageinit", function () {
            if (!mouseHandlerSet) {
                $("#page1 a").mouseenter(function (e) {
                    $("#popup").popup("open", {
                        x: e.pageX, y: e.pageY
                    });
                });
                $("#popup").mouseleave(function (e) {
                    $(this).popup("close");
                });
                mouseHandlerSet = true;
            }
        });
    </script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <p><a href="#popup" data-rel="popup">Popup</a></p>
        </div>
        <div id="popup" data-role="popup" data-history="false">
            <p>This is the popup content</p>
        </div>
    </div>
</body>
</html>

这个例子使用了弹出菜单openclose方法,我将在本章后面描述,这样当用户将鼠标或指针移到a元素上时弹出菜单就会显示,当用户将鼠标移出弹出菜单时弹出菜单就会隐藏(我使用了一个传递给open方法的配置选项来确保弹出菜单在鼠标指针下打开)。

在这种情况下,用户可能不希望后退按钮简单地关闭弹出窗口,因为首先没有执行显式导航来打开它——这是考虑data-history 属性以创建一致体验的好机会。

image 提示我正在仔细限定这个配置设置的使用,因为理解这个机制只是围绕弹出窗口使用的混乱的一部分。另一个问题是决定采用哪种方法——这只能在应用的其余部分中决定。这种情况下的用户期望很难预测,只有用户测试才能告诉你哪种方法能为你的 web 应用创造自然的体验。不要试图逃避测试,简单地将它作为一个可配置的选项——大多数用户将不一致的行为归因于糟糕的实现,并且不会努力去看看是否可以改变。

使用弹出窗口呈现丰富的内容

弹出窗口可以用来呈现丰富的内容,弹出窗口的一个常见用途是向用户呈现一组图像缩略图,这些缩略图可以在弹出窗口中打开全尺寸图像,这就是我在清单 29-12 中所做的。(我在本书附带的apress.com的免费下载中包含了这个例子中的图片。)

清单 29-12 。使用弹出菜单显示丰富的内容

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style>
        .smallImage { height: 40%; width: 40%; padding: 5px}
    </style>
    <script>
        $(document).bind("pageinit", function () {

            var data = ["beach.png", "clouds.png", "fishing.png", "storms.png"];
            for (var i = 0; i < data.length; i++) {
                $("<img>").addClass("smallImage").attr("src",
                    data[i]).appendTo("#contentHolder");
            }

            $("#popup").popup({
                corners: false,
                overlayTheme: "a"
            });

            $("#contentHolder img").bind("tap", function (e) {
                var maxHeight = $(window).height() - 10 + "px";
                $("#LgImage").attr("src", e.target.src).css("max-height", maxHeight);
                $("#popup").popup("open");
            });
        });
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="d">
        <div id="contentHolder" data-role="content"></div>
        <div id="popup" data-role="popup" data-history="true">
            <img id="LgImage" class="zoomImg" src="" />
        </div>
    </div>
</body>
</html>

我使用一个for循环来生成一组四个缩略图img元素,我将它们添加到 jQuery Mobile 页面,并使用标准 CSS 来确保它们的大小都相同。我为tap事件创建一个处理程序(我在第二十七章中描述过),它将选定的图像设置为弹出窗口的内容,并调用open方法将其显示给用户(我在本章后面的使用弹出窗口方法一节中描述了 open 方法,但现在只要知道调用它将显示弹出窗口就足够了)。你可以在图 29-10 中看到效果:用户点击其中一个缩略图,一个更大的版本显示在弹出窗口中。

9781430263883_Fig29-10.jpg

图 29-10 。使用弹出窗口显示图像

我混合使用了数据属性和设置来配置弹出窗口,只是为了展示两者的使用情况。弹出窗口下面的div元素具有data-history属性,如下所示:

...
<div id="popup" data-role="popup"data-history="true">
    <img id="LgImage" class="zoomImg" src="" />
</div>
...

这是data-history属性的缺省值,但是我喜欢具体说明这个设置,因为它会引起很多混乱。我使用了script元素中的popup方法来设置其他选项,如下所示:

...
$("#popup").popup({
    corners: false,
    overlayTheme: "a"
});
...

使用圆角会裁剪掉大图的一部分,所以我使用corners设置来指定圆角。我想在弹出窗口显示时将用户的注意力集中在大图像上,所以我使用了overlayTheme设置来为弹出窗口提供深色背景。

当使用弹出窗口显示可能比弹出窗口本身更大的内容时,需要做一些额外的工作,因为默认情况下,内容会滚动。为了避免这种情况,我获取了窗口的高度,并将其作为设置弹出窗口中图像高度的基础,如下所示:

...
var maxHeight = $(window).height() - 10 + "px";
$("#LgImage").attr("src", e.target.src).css("max-height", maxHeight);
...

我让图像比窗口小 10 个像素,这给了我一个小边框来强调用户看到了一个弹出窗口。理论上,我应该将我的边框与tolerance设置对齐,但是在我写这个的时候这是不可靠的,所以我只是使用一个显式值来获得我想要的效果。我把序列中的第一个缩略图beach.png图像做得比其他的大,在图 29-11 中,你可以想象如果我不设置 CSS max-height属性的设置会发生什么:图像的顶部和底部不显示,也没有视觉提示指示用户可以滚动查看图像的其余部分。

9781430263883_Fig29-11.jpg

图 29-11 。显示比弹出窗口大的内容的效果

使用弹出方法

弹出窗口小部件定义了表 29-8 中所示的方法。

表 29-8 。弹出方法

方法 描述
popup("open") 打开弹出窗口。
popup("close") 关闭弹出窗口。
popup("reposition") 更改弹出窗口的位置。

我在前面的例子中演示了open方法的基本用法,但是您可以提供一个可选的参数来配置弹出窗口的打开方式,提供与用于a元素的data属性等效的功能。可选参数是一个对象,其属性名如表 29-9 所示。

表 29-9 。open 方法的可选参数的属性

名字 描述
x 指定弹出窗口应该显示的 X 坐标。
y 指定应显示弹出窗口的 Y 坐标。
transition 指定用于动画弹出窗口的过渡–参见第二十八章了解 jQuery Mobile 过渡的详细信息。
positionTo 使用表 29-6 中描述的值指定弹出窗口的位置。

reposition方法 也接受一个配置对象参数,您可以使用xy,positionTo属性来指定弹出窗口的新位置。close方法不接受任何参数,只是取消弹出窗口。在清单 29-13 中,你可以看到所有三种正在使用的方法。

清单 29-13 。使用弹出方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("button").bind("vmousedown", function (e) {
                var pop = $("#popup");
                switch (e.target.innerText) {
                    case "Open":
                        pop.popup("open", {
                            x: 10, y: 10, transition: "fade"
                        });
                        break;
                    case "Close":
                        pop.popup("close");
                        break;
                    default:
                        pop.popup("reposition", {
                            positionTo: e.target.innerText == "Window"
                                ? "window" : "#page1 button"
                        });
                        break;
                }
            });
        });
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="d">
        <div data-role="content">
            <button>Open</button>
        </div>
        <div id="popup" data-role="popup" data-history="true"
                data-dismissible="false">
            <button>Selector</button>
            <button>Window</button>
            <button>Close</button>
        </div>
    </div>
</body>
</html>

清单中的 jQuery Mobile 页面包含一个按钮,我通过调用弹出菜单上的open方法来处理该按钮的vmousedown事件。我传入可选的配置对象来指定弹出窗口应该显示在带有fade过渡的特定位置(x 轴和 y 轴上距离左上角 10 个像素)。

popup包含附加按钮。Close按钮调用close方法隐藏弹出窗口,其他按钮调用reposition方法指定弹出窗口应该移动到的不同位置。你可以在图 29-12 中看到结果。

9781430263883_Fig29-12.jpg

图 29-12 。使用弹出方法

image 提示注意,在这个例子中,我已经将data-dismissible属性设置为false。如果没有这个设置,大多数触摸浏览器产生的合成鼠标事件(如第二十七章所述)将被解释为弹出窗口之外的点击,导致弹出窗口在打开后立即关闭。将data-dismissible属性设置为false可以确保这种情况不会发生,并且弹出窗口只能被调用close方法的按钮关闭。

使用弹出事件

jQuery Mobile popup 小部件定义了表 29-10 中显示的事件。

表 29-10 。弹出事件

事件 描述
create 创建弹出窗口小部件时触发。
beforeposition 在弹出窗口重新定位之前触发。
afteropen 弹出窗口显示后触发。
afterclose 隐藏弹出窗口后触发。

我很少发现这些事件有用,我对弹出窗口小部件的使用通常仅限于方法和配置选项。一个例外是当我想在有限的时间内显示一个弹出窗口时,在这种情况下,afteropen事件会很有用,如清单 29-14 所示。

清单 29-14 。处理弹出事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("#popup").popup({
                afteropen: function (e) {
                    setTimeout(function () {
                        $("#popup").popup("close");
                    }, 5000);
                }
            });
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="#popup" data-rel="popup">Show the popup</a>
        </div>
        <div id="popup" data-role="popup" data-position-to="window">
            <p>This is the popup content</p>
        </div>
    </div>
</body>
</html>

image 警告我只在无法与客户协商替代方案时使用这种技巧。我的感觉是,弹出窗口应该完全由用户控制,让它们以与用户交互没有直接联系的方式出现和消失会引起混乱。

在这个例子中,我处理afteropen方法调用setTimeout函数来注册一个将在五秒钟后执行的回调。回调函数调用close方法来关闭弹出窗口。

摘要

在本章中,我介绍了前两个 jQuery Mobile 小部件:对话框和弹出窗口。jQuery Mobile 小部件遵循与 jQuery UI 小部件相同的基本结构和原理,但是针对移动设备进行了明显的优化。在第三十章中,我描述了按钮和可折叠块部件。

三十、按钮和可折叠块

在这一章中,我描述了另外两个 jQuery Mobile 部件:按钮可折叠块。jQuery Mobile button widget 的工作方式与您在本书前面看到的 jQuery UI 按钮类似,只是您可以创建和使用简单的按钮,而无需使用任何定制的 JavaScript 代码,而是依靠data属性。

可折叠块类似于手风琴的单个面板;事实上,您可以单独使用可折叠积木,或者将几个积木组合在一起,形成一个简单的手风琴。表 30-1 对本章进行了总结。我还向您展示了在同一主题上提供变体的小部件——导航条,它对按钮进行分组以提供一致的导航支持,以及可折叠集,它可用于创建折叠式小部件。

表 30-1 。章节总结

问题 解决办法 列表
自动创建一个按钮部件。 添加一个类型为submitresetbuttonbutton元素或input元素。 one
从其他元素创建按钮部件。 应用值为buttondata-role属性。 Two
创建一组按钮。 使用值为controlgroupdata-role属性。使用data-type属性改变方向。 3, 4
在按钮上添加和定位图标。 使用data-icondata-iconpos属性。 five
创建更小的按钮。 使用data-minidata-inline属性。 six
更新按钮以反映基础元素中的更改。 使用refresh方法。 seven
响应按钮事件。 处理来自基础元素的事件。 eight
提供一致的导航按钮。 使用导航条小部件。 nine
将图标放置在导航栏中。 使用data-iconpos属性 Ten
创建可折叠块。 应用值为collapsibledata-role属性。确保有一个 header 元素作为第一个子元素。 11, 12
当块折叠或展开时接收通知。 处理collapseexpand事件。 Thirteen
创建一个手风琴。 使用值为collapsible-setdata-role属性。 Fourteen

使用 jQuery Mobile 按钮

我已经在前面的例子中使用了一些按钮部件,但是现在是时候返回来解释它们是如何工作的了。

创建按钮小部件

作为自动页面增强过程的一部分,jQuery Mobile 从button元素或从类型属性设置为submitresetbuttonimageinput元素创建按钮小部件。您不必对这些元素类型采取任何特殊的操作,因为 jQuery Mobile 会为您完成所有的工作。清单 30-1 显示了一个包含一些自动增强元素的页面。

清单 30-1 。依靠按钮部件的自动创建

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button>Button</button>
            <input type="submit" value="Input (Submit)" />
            <input type="reset" value="Input (Reset)" />
            <input type="button" value="Input (Button)" />
        </div>
    </div>
</body>
</html>

你可以在图 30-1 中看到如何为每种类型的元素创建一个按钮小部件。

9781430263883_Fig30-01.jpg

图 30-1 。jQuery Mobile 自动创建的按钮部件

从其他元素创建按钮

jQuery Mobile 还可以从其他元素创建按钮小部件。在前面的章节中,您看到了我通过应用值为buttondata-role属性,从a元素创建一个按钮小部件。您也可以对其他类型的元素这样做,比如div。清单 30-2 包含了一个例子。

清单 30-2 。从其他元素创建按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <a href="document2.html" data-role="button">A Element</a>
            <div data-role="button">DIV Element</div>
        </div>
    </div>
</body>
</html>

你可以在图 30-2 中看到 jQuery Mobile 如何处理这个例子中的元素。

9781430263883_Fig30-02.jpg

图 30-2 。使用其他元素创建按钮部件

创建分组按钮

您可以通过创建一个控件组来创建一组没有间距的按钮。您可以通过将值为controlgroupdata-role属性应用于两个或更多按钮小部件的父元素来实现这一点。清单 30-3 提供了一个演示。

清单 30-3 。创建一组分组按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <div data-role="controlgroup">
                <button>Back</button>
                <button>Home</button>
                <button>Next</button>
            </div>
        </div>
    </div>
</body>
</html>

在这个例子中,有三个按钮,它们都是一个div元素的子元素,这个元素的data-role是控制组。你可以在图 30-3 中看到效果。注意只有顶部和底部的按钮有圆角。

9781430263883_Fig30-03.jpg

图 30-3 。显示在组中的一组按钮

您可以通过将data-type属性设置为horizontal来改变按钮组的方向,如清单 30-4 所示。

清单 30-4 。创建水平按钮组

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <div data-role="controlgroup" data-type="horizontal">
                <button>Back</button>
                <button>Home</button>
                <button>Next</button>
            </div>
        </div>
    </div>
</body>
</html>

你可以在图 30-4 中看到浏览器是如何显示水平按钮组的。请再次注意圆角是如何仅应用于外部元素的。

9781430263883_Fig30-04.jpg

图 30-4 。创建水平按钮组

配置 jQuery Mobile 按钮

jQuery Mobile 定义了许多数据属性和配置设置,您可以使用它们来配置按钮和从不同的元素类型创建按钮。这些属性在表 30-2 中有所描述,我将在接下来的章节中演示这些按钮特有的属性。

表 30-2 。数据属性和按钮配置设置

数据属性 环境 描述
data-corners corners true时,按钮用圆角绘制。值false意味着将使用方形拐角。默认是true
data-icon icon 指定要在按钮中使用的图标。
data-iconpos iconpos 如果使用了图标,则指定图标的位置。
data-iconshadow iconshadow 设置为true时,对图标应用阴影。
data-inline inline 创建一个按其内容调整大小的按钮(而不是填满屏幕)。
data-mini mini 当设置为true时,显示一个紧凑按钮。
data-shadow shadow true时,按钮用阴影绘制。值false意味着不使用阴影。默认是true

向按钮添加图标

jQuery Mobile 包括一组可以在按钮中使用的图标。这些都包含在你在第二十七章中安装的images目录下的一个镜像文件中。表 30-3 显示了图标名称列表和每个图标的简要描述。

表 30-3 。jQuery Mobile 中包含的图标

图标名称 描述
arrow-l``arrow-r``arrow-u 面向左、右、上、下的箭头。
bars 一组三条水平线。
edit 铅笔,用于表示用户可以编辑内容。
check delete 一张支票和一个十字。
plus minus 加号和减号。
gear 一个齿轮。
refresh``forward``back``home 用于刷新、前进到下一页、返回到上一页、返回到主页或搜索的浏览器样式图标。
grid 一个由小方块组成的网格。
star 一颗星星。
alert 小心警告。
info 程式化的字母 I。

使用data-icon属性将图标应用于按钮,其中的值指定了要使用的图标的名称。使用data-iconpos属性来指定图标在按钮中的位置。默认为left,但也可以指定toprightbottom。如果将data-iconpos设置为notext,则仅显示图标。清单 30-5 提供了一个使用这两种属性的例子。

清单 30-5 。向按钮添加图标

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <div class="ui-grid-b">
                <div class="ui-block-a">
                    <button data-icon="home">Home</button>
                </div>
                <div class="ui-block-b">
                    <button data-icon="home" data-iconpos="top">Home</button>
                </div>
                <div class="ui-block-c">
                    <button data-icon="home" data-iconpos="notext"></button>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

在这个例子中,我创建了三个按钮,它们都显示了home图标。第一个按钮使用默认的图标位置,第二个按钮使用top位置,最后一个按钮使用notext值,这将创建一个只有图标的按钮。你可以在图 30-5 中看到这些按钮是如何出现的。

9781430263883_Fig30-05.jpg

图 30-5 。创建图标按钮

你可以看到每个按钮都有独特的风格。最引人注目的是最后一个按钮,它不显示任何文本。这看起来很吸引人,但是我对这种按钮的体验是,它们很难用手指点击,并且不是所有的用户都能立即识别出它们是在应用中导航的一种方式。

创建内嵌和迷你按钮

默认情况下,jQuery Mobile 按钮跨越整个屏幕宽度。你可以在图 30-1 中看到默认按钮宽度的例子。在后面的例子中,我使用了布局网格来创建更小的按钮,但是我可以使用内嵌按钮来实现类似的效果,这些按钮的大小刚好能够容纳它们的内容,并且是通过将data-inline属性设置为true来创建的。

我还可以通过将data-mini属性设置为true来创建迷你按钮。迷你按钮有标准的宽度,但是占据更少的垂直空间。最后,你可以结合这两种数据属性来创建内嵌的迷你按钮,它们的宽度刚好能容纳它们的内容比普通按钮占据更少的垂直空间。清单 30-6 显示了所有三种类型的按钮。

清单 30-6 。使用内嵌和迷你按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button data-icon="home" data-inline="true">Home</button>
            <button data-icon="home" data-mini="true">Home</button>
            <button data-icon="home" data-inline="true" data-mini="true">Home</button>
        </div>
    </div>
</body>
</html>

你可以在图 30-6 中看到效果。

9781430263883_Fig30-06.jpg

图 30-6 。使用内嵌和迷你按钮

使用按钮方法

按钮部件定义了三种方法,我已经在表 30-4 中描述过了。

表 30-4 。按钮方法

方法 描述
button("disable") 禁用按钮,使其不能被点击。
button("enable") 启用按钮,以便可以单击它。
button("refresh") 刷新小部件以反映基础 HTML 元素中的更改。

enabledisable方法不言而喻。当底层buttoninput元素的内容改变时,需要refresh方法,如清单 30-7 中的所示。

清单 30-7 。更新按钮部件的内容

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            var counter = 0;  
            setInterval(function () {
                var msg = "Counter " + counter++;  
                $("#buttonElem").text(msg).button("refresh");  
                $("#inputElem").val(msg).button("refresh");  
                $("#divElem span.ui-btn-text").text(msg);  
            }, 1000);  
        });  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button id="buttonElem">Button</button>
            <input id="inputElem" type="button" value="Input" />
            <div id="divElem" data-role="button">Div</div>
        </div>
    </div>
</body>
</html>

本例中有三个按钮小部件,分别由一个button元素、一个input元素和一个div元素创建。script元素通过调用setInterval函数每秒更新支撑按钮部件的元素内容来处理pageinit事件(我在第二十九章中描述过)。

我必须为buttoninput元素调用refresh方法;否则,我对元素所做的更改将不会反映在按钮小部件中:

...
$("#buttonElem").text(msg).button("refresh");  
$("#inputElem").val(msg).button("refresh");  
...

当您从其他元素(如div元素)创建按钮小部件时,需要一种不同的方法。在这种情况下,jQuery Mobile 添加内容元素,这样它就可以应用 CSS 样式来调整小部件的形状,更改元素的内容会影响小部件的形状。相反,您需要查看 jQuery Mobile 在元素内部生成的 HTML,并定位文本内容。我的示例中的div元素由span元素填充,我可以通过定位属于ui-btn-text类的元素来更改文本内容,如下所示:

...
$("#divElem span.ui-btn-text").text(msg);  
...

image 提示我通过使用浏览器 F12 工具来浏览 jQuery Mobile 在增强过程中生成的内容,从而找到了这个元素。

使用按钮事件

按钮小部件定义了标准的create事件,该事件在小部件被创建时被触发,但是支撑小部件的小部件仍然会触发它们自己的事件,这将由我在第二十九章中描述的 jQuery Mobile 事件来补充。这意味着您可以为tap事件创建处理程序,例如,当按钮被点击时接收通知,如清单 30-8 所示。

image 提示不要忘记事件可以对某些元素有一个默认动作——例如,这意味着从input元素创建的按钮部件将提交它们所属的表单。jQuery 事件处理详见第二十八章。

清单 30-8 。处理按钮事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("button").tap(function (e) {
                $(this).text("Tapped!").button("refresh");  
            });  
        });  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <button>Button</button>
        </div>
    </div>
</body>
</html>

在这个清单中,我使用tap方法为按钮上的tap事件注册一个处理函数。当单击button时,我通过更改按钮文本并调用refresh方法来更新小部件内容来处理事件。

使用 jQuery Mobile 导航栏

导航栏是在页眉或页脚中提供导航支持的一组按钮,允许用户浏览一组内容页面。导航条非常简单,但是当你有一组相关的页面要显示,并且你需要给用户一个清晰的信号,告诉他们当前显示的是哪个页面的时候,导航条是一个非常有用的工具。导航条是使用特定的元素结构创建的,你可以在清单 30-9 中看到。

清单 30-9 。创建一个导航条

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
            <h1>Jacqui's Shop</h1>
            <div data-role="navbar">
                <ul>
                    <li><a href="#page1"
                        class="ui-btn-active ui-state-persist">Page 1</a></li>
                    <li><a href="#page2">Page 2</a></li>
                    <li><a href="#page3">Page 3</a></li>
                </ul>
            </div>
        </div>
        <div data-role="content">This is page 1</div>
    </div>

    <div id="page2" data-role="page">
        <div data-role="header">
            <h1>Jacqui's Shop</h1>
            <div data-role="navbar">
                <ul>
                    <li><a href="#page1">Page 1</a></li>
                    <li><a href="#page2"
                        class="ui-btn-active ui-state-persist">Page 2</a></li>
                    <li><a href="#page3">Page 3</a></li>
                </ul>
            </div>
        </div>
        <div data-role="content">This is page 2</div>
   </div>

    <div id="page3" data-role="page">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
            <div data-role="navbar">
                <ul>
                    <li><a href="#page1">Page 1</a></li>
                    <li><a href="#page2">Page 2</a></li>
                    <li><a href="#page3"
                        class="ui-btn-active ui-state-persist">Page 3</a></li>
                </ul>
            </div>
        </div>
        <div data-role="content">This is page 3</div>
   </div>
</body>
</html>

导航条由一个div元素定义,该元素的data-role属性被设置为navbar。导航栏中的各个按钮被定义为包含在li元素中的a元素,而ul元素又包含在a元素中。在清单中,我使用了 Navbar 最常见的形式,它在多个 jQuery Mobile 页面中重复出现,为用户提供一致的引用,如图 30-7 所示。

9781430263883_Fig30-07.jpg

图 30-7 。导航栏小部件

NavBar 小部件在导航按钮之间平均分配可用的屏幕空间,单击其中一个按钮会显示由底层a元素的href属性标识的页面。

有两个 CSS 类必须应用于a元素,以向用户指示页面正在显示。ui-btn-active类将按钮标记为活动状态,而ui-state-persist类确保在用户导航回之前显示的页面时显示活动状态。

配置 jQuery Mobile 导航栏

导航条只支持一个数据属性/配置选项,我已经在表 30-5 中描述过了。

表 30-5 。导航栏的数据属性和配置设置

数据属性 环境 描述
data-iconpos iconpos 指定图标在导航栏按钮中的位置。支持的位置有leftrighttop(默认)和bottom。你也可以使用notext值来显示一个没有任何文本的图标。

我在本章前面列出了 jQuery Mobile 图标。使用data-icon属性将单个图标应用于a元素,您可以看到我是如何在清单 30-10 中定位带有data-iconpos属性的图标的。

清单 30-10 。在导航条中定位图标

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page">
        <div data-role="header">
            <h1>Jacqui's Shop</h1>
            <div data-role="navbar" data-iconpos="left">
                <ul>
                    <li><a href="#page1" data-icon="alert"
                        class="ui-btn-active ui-state-persist">Page 1</a></li>
                    <li><a href="#page2" data-icon="info">Page 2</a></li>
                    <li><a href="#page3" data-icon="gear">Page 3</a></li>
                </ul>
            </div>
        </div>
        <div data-role="content">This is page 1</div>
    </div>
</body>
</html>

你可以在图 30-8 中看到图标的效果。

9781430263883_Fig30-08.jpg

图 30-8 。在导航栏中添加和定位图标

使用导航栏方法&事件

NavBar 小部件没有定义任何方法,仅支持create事件,该事件在小部件被实例化时触发。

image 提示处理来自a元素的事件以响应用户导航。

使用可折叠的内容块

jQuery Mobile 支持创建可折叠的内容块,这些内容块是带有标题的内容部分,可以被关闭,这样只有标题是可用的。这类似于 jQuery UI 手风琴的单个面板,我在第十九章中描述过。

创建可折叠块

为了给 jQuery Mobile 提供它需要的元素,可折叠块有一个特定的结构。清单 30-11 包含了一个例子。

清单 30-11 。创建单个可折叠块

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <div data-role="collapsible">
                <h1>New Delivery Service</h1>
                <p>We are pleased to announce that we are starting a home delivery
                service for your flower needs. We will deliver within a 20 mile radius
                of the store for free and $1/mile thereafter.</p>
            </div>
        </div>
    </div>
</body>
</html>

您要做的第一件事是创建一个div元素,并应用值为collapsibledata-role属性。这告诉 jQuery Mobile 您想要一个可折叠的块,它应该寻找一个 header 元素作为div的第一个子元素。您可以使用任何标题元素,h1h6。我使用了一个h1元素,但是 jQuery Mobile 对这种小部件的所有标题一视同仁。div的其余子元素用作可折叠元素的内容,产生如图 30-9 所示的效果。

9781430263883_Fig30-09.jpg

图 30-9 。展开可折叠块

第一次显示时,该块是折叠的,这意味着内容是隐藏的,只能看到标题。作为对用户的提示,在标题区域的左边缘绘制了一个加号图标(其样式与非内联按钮相同)。单击标题会显示内容并将图标替换为减号,表示该块可以再次折叠。

配置 jQuery Mobile 可折叠内容块

jQuery Mobile 定义了表 30-6 中所示的数据属性和配置设置。

表 30-6 。可折叠块的数据属性和配置设置

数据属性 环境 描述
data-collapsed collapsed true为默认值时,块显示为折叠状态(即,用户只能看到标题)。值false意味着块被展开显示。
data-collapsed-icon collapsedIcon 指定块折叠时显示的图标。
data-content-theme contentTheme 指定可折叠块内容区域的主题。
data-corners corners true时,可折叠块用圆角绘制。值false产生方形角。
data-expanded-icon expandedIcon 指定块展开时显示的图标。
data-iconpos iconPos 指定图标在标题中的位置,使用与导航条和按钮部件相同的值。
data-inset inset 当设置为false时,标题将填充窗口,没有任何填充。默认为true
data-mini mini 设置为true时,标题以紧凑形式绘制。

在清单 30-12 中,你可以看到我是如何将一些数据属性应用到这个例子中的。

清单 30-12 。配置可折叠块小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <div data-role="collapsible" data-content-theme="e" data-collapsed="false"
                    data-inset="false" data-iconpos="top">
                <h1>New Delivery Service</h1>
                <p>We are pleased to announce that we are starting a home delivery
                service for your flower needs. We will deliver within a 20 mile radius
                of the store for free and $1/mile thereafter.</p>
            </div>
        </div>
    </div>
</body>
</html>

我已经更改了data-content-themedata-insetdata-iconposdata-collapsed属性的值。你可以在图 30-10 中看到效果。

9781430263883_Fig30-10.jpg

图 30-10 。配置可折叠块小部件

使用可折叠块方法

可折叠块小部件没有定义任何方法。

使用可折叠块事件

可折叠块微件定义了表 30-7 中所示的事件。

表 30-7 。可折叠块事件

事件 描述
create 创建小部件时会触发此事件。
collapse 当可折叠块折叠时触发。
expand 当可折叠块展开时触发。

在清单 30-13 中,您可以看到我是如何处理collapseexpand事件来报告可折叠块的状态的。如果这看起来像是一个人为的例子,那是因为这些事件很少有用。

清单 30-13 。使用折叠和展开事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function() {
            $('#colBlock').bind("collapse expand", function(event) {
                $('#status').text(event.type == "expand" ? "Expanded" : "Collapsed");
            })  
        });  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            The block is <b><span id="status">Expanded</span></b>

            <div id="colBlock" data-role="collapsible" data-content-theme="e"
                    data-collapsed=false>
                <h1>New Delivery Service</h1>
                <p>We are pleased to announce that we are starting a home
                delivery service for your flower needs. We will deliver within a
                20 mile radius of the store for free and $1/mile thereafter.</p>
            </div>
        </div>
    </div>
</body>
</html>

在这个例子中,我使用了bind方法来监听expandcollapse事件。我在对bind方法的一次调用中通过列出我感兴趣的事件来做到这一点,用空格分隔。当其中一个事件被触发时,我更新一个span元素的内容来反映可折叠块的状态。在图 30-11 中可以看到状态的变化。

9781430263883_Fig30-11.jpg

图 30-11 。响应展开和折叠事件

使用 jQuery Mobile 可折叠集(折叠)

可折叠的 set widget 组合多个可折叠的块来创建一个手风琴。可折叠集合是通过使用将两个或更多可折叠块包装在单个父元素中的div元素,并将data-role属性应用于值为collapsible-setdiv元素来定义的。你可以在清单 30-14 中看到这是如何做到的。

清单 30-14 。创建 jQuery Mobile 手风琴

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <div data-role="collapsible-set" data-content-theme="e">
                <div data-role="collapsible">
                    <h1>New Delivery Service</h1>
                    <p>We are pleased to announce that we are starting a home
                    delivery service for your flower needs. We will deliver within a
                    20 mile radius of the store for free and $1/mile thereafter.</p>
                </div>
                <div data-role="collapsible" data-collapsed=false>
                    <h1>Summer Specials</h1>
                    <p>We have a wide range of special summer offers.
                        Ask instore for details</p>
                </div>
                <div data-role="collapsible">
                    <h1>Bulk Orders</h1>
                    <p>We offer discounts for large orders. Ask us for prices</p>
                </div>
            </div>
        </div>
    </div>
</body>
</html>

在这个例子中,我用data-role属性的collapsible-set值定义了一个div元素,它包含三个可折叠块。

image 提示注意,我将data-content-theme属性应用于外部容器。这与在每个单独的可折叠块上使用属性具有相同的效果。

默认情况下,所有可折叠的块最初都是折叠的,所以我将data-collapsed属性应用于其中一个值为false的块,以便它在页面第一次显示时展开。当用户点击标题时,当前展开的元素被折叠。你可以在图 30-12 中看到效果。

9781430263883_Fig30-12.jpg

图 30-12 。在 jQuery Mobile 手风琴中展开块

配置可折叠集合

可折叠集支持与可折叠块小部件相同的一组数据属性和设置。

使用可折叠设置方法

可折叠设置小部件支持一种方法,如表 30-8 所述。

表 30-8 。可折叠集合方法

方法 描述
collapsibleset("refresh") 刷新小部件以反映基础 HTML 元素中的更改。

使用可折叠设置事件

可折叠的 set 小部件只定义了create事件,该事件在小部件应用于 HTML 元素时被触发。

摘要

在这一章中,我描述了更多的 jQuery Mobile 小部件:按钮、可折叠块和小部件,它们提供了相同主题的变体——导航条和可折叠集合。在第三十一章中,我向您展示了 jQuery Mobile 表单小部件。

三十一、使用 jQuery Mobile 表单

当在移动设备上呈现表单时,存在特别的困难。开始时屏幕空间很小,您需要为用户提供易于触摸操作的表单元素,而不需要创建需要无休止滚动才能完成的页面。在这一章中,我将向您展示 jQuery Mobile 如何增强表单元素,使它们与其他小部件保持一致,并且可以很容易地在触摸屏上使用。

jQuery Mobile 在页面加载时会自动完成大量工作。表单元素在没有您干预的情况下得到增强,并且在提交表单时自动使用 Ajax,以便 jQuery Mobile 可以平稳地转换到服务器返回的结果。

我建议在为移动设备创建表单时仔细考虑。就其本质而言,表单旨在收集用户的输入,但这在移动设备上可能是一个乏味的过程,尤其是在输入时。此外,当用户不主动滚动页面时,一些移动设备不会显示滚动条。这意味着用户并不总是意识到有表单元素就在即时显示之外。要为用户创造最佳体验,您需要遵循一些基本准则:

  • 需要尽可能少的打字。在可能的情况下,使用允许用户进行简单触摸选择的替代部件,比如复选框或单选按钮。这可能会缩小用户可以输入的范围,但可能会增加愿意完成表单的用户数量。
  • 使用页面间导航来显示表单的各个部分。这给用户一个清晰的进度指示,不需要他们推测性地滚动来查看是否遗漏了什么。
  • 删除任何不需要的表单元素。移动表单应该尽可能精简,这意味着从移动用户那里接受的数据要比从桌面用户那里接受的少。

表 31-1 对本章进行了总结。

表 31-1 。章节总结

问题 解决办法 列表
为表单元素创建小部件。 不需要特定的操作——jQuery Mobile 会自动应用小部件。 one
添加一个按钮来清除 input 元素的内容,或者更改 input 元素的小部件的显示方式。 使用data-clear-btndata-mini属性。 Two
启用或禁用输入元素。 使用enabledisable方法。 three
创建一个滑块小部件。 定义一个类型属性为rangeinput元素。 four
创建一个范围滑块小部件。 在一个div元素中定义一对input元素,该元素的data-role属性被设置为rangeslider five
配置滑块的外观。 使用data-highlightdata-minidata-track-theme属性。 6, 7
更新范围滑块以反映底层input元素的变化。 使用refresh方法。 eight
使用滑块小工具时接收通知。 处理startstopnormalize事件。 9, 10
为一个select元素创建一个小部件。 不需要特定的操作。 Eleven
配置为select元素显示的按钮。 使用数据角、data-icondata-iconposdata-mini属性。 Twelve
配置允许用户为select元素选择值的弹出窗口。 使用data-native-menudata-overlay-themedata-divider-theme属性。 Thirteen
select菜单添加占位符。 select元素包含的option元素之一的data-placeholder属性设置为true Fourteen
以编程方式控制 selectmenu 小工具。 使用openclosedisableenablerefresh方法。 Fifteen
创建翻转开关小部件。 定义一个包含两个option元素并且将data-role属性设置为sliderselect元素。 Sixteen
创建复选框和单选按钮小部件。 不需要特定的操作。 17–20

创建表单元素小部件

jQuery Mobile 使用自动增强功能在加载 HTML 页面时为表单元素创建小部件,就像我在前面章节中描述的小部件一样。清单 31-1 展示了一个 jQuery Mobile 页面,其中包含一个form元素和一些与表单相关的子元素。

清单 31-1 。jQuery Mobile 页面中的简单表单

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
        div[data-role=fieldcontain] { padding: 0 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="name">Name: </label>
                <input id="name">
            </div>
            <div data-role="fieldcontain">
                <label for="address">Address: </label>
                <textarea id="address"></textarea>
            </div>
            <div id="buttonContainer">
                <input type="submit" data-inline="true" value="Submit"/>
            </div>
        </form>
    </div>
</body>
</html>

这是一个简单的表单,但是它为 jQuery Mobile 如何处理整个表单奠定了基础。有两个表单元素:一个文本input 和一个textarea ,每个元素都与一个label元素成对出现。您可以在图 31-1 中看到结果,这是我使用 BrowserStack 服务创建的,因为 Opera Mobile 模拟器没有正确实现 jQuery Mobile 依赖于表单元素的一个特性,我将在下一节解释这个特性。

9781430263883_Fig31-01.jpg

图 31-1 。jQuery Mobile 显示的简单表单

image 提示注意,我已经使用 CSS 为包含表单元素的div元素设置了padding属性。如果没有这个设置,jQuery Mobile 将会沿着窗口的左右边缘绘制标签和表单元素。

当表单元素中包含类型为submitinput元素时,jQuery Mobile 将自动提交表单。默认情况下,表单将使用 Ajax 提交,但是可以通过将值为falsedata-ajax属性应用到form元素来禁用这种行为。

在最后一个例子中,每个表单元素及其label都被包装在一个div元素中。我将div元素的data-role设置为fieldcontain,这告诉 jQuery Mobile 我希望label和表单元素显示在一行中,你可以在图 31-1 中看到效果。

jQuery Mobile 应用于对齐标签和表单元素的样式仅在屏幕宽度至少为 450 像素时使用。在该宽度值之下,labelinput / textarea元素显示在不同的行上,如图 31-2 中的所示。Opera Mobile Emulator 总是报告其屏幕宽度小于 450 像素,这就是为什么我不得不使用不同的浏览器来生成这些示例的图形。(这只是模拟器的问题——真正的 Opera 移动浏览器正确实现了该功能。)

9781430263883_Fig31-02.jpg

图 31-2 。纵向显示表单

使用 Textinput 小工具

jQuery Mobile 对它支持的每个表单元素使用不同的小部件,我描述的第一个小部件是用于textarea元素和input元素的文本输入小部件。这是上一个示例中使用的小部件。

配置 文本输入小部件

textinput 小部件定义了表 31-2 中描述的属性和设置特性。通过textinput方法应用设置。

表 31-2 。TextInput 小工具的属性 和配置设置

数据属性 环境 描述
data-clear-btn clearBtn 当设置为true时,微件显示有一个清除内容的按钮。默认为false
data-clearn-btn-text clearBtnText 为辅助功能软件设置清除按钮的文本。
data-mini mini 当设置为true时,创建小版本的微件。默认为false
data-prevent-focus-zoom preventFocusZoom 当设置为true时,当小工具具有焦点时,浏览器被阻止缩放。

在清单 31-2 中,您可以看到我是如何应用data-clear-btndata-mini属性来配置input元素上的 textinput 小部件的。

清单 31-2 。配置 Textinput 部件

...
<form method="get">
    <div data-role="fieldcontain">
        <label for="name">Name: </label>
        <input id="name" data-clear-btn="true" data-mini="true">
    </div>
    <div data-role="fieldcontain">
        <label for="address">Address: </label>
        <textarea id="address"></textarea>
    </div>
    <div id="buttonContainer">
        <input type="submit" data-inline="true" value="Submit"/>
    </div>
</form>
...

你可以在图 31-3 中看到结果。直到用户开始在input元素中输入文本,清除按钮才会显示,点击该按钮会删除任何已经输入的内容。如果您使用 clear 按钮特性,那么一定要对所有的input元素保持一致。

9781430263883_Fig31-03.jpg

图 31-3 。配置 textinput 部件

使用 Textinput 小部件方法

textinput 小部件定义了表 31-3 中所示的两种方法。

表 31-3 。文本输入方法

方法 描述
textinput("disable") 禁用小部件,防止用户输入或编辑内容。
textinput("enable") 启用小部件,允许输入或编辑内容。

在清单 31-3 中,我使用了两种方法来改变一个input元素的状态。

清单 31-3 。使用 Textwidget 方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("button").tap(function (e) {
                $("#name").textinput(e.target.id);
                e.preventDefault();
            });  
        });  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
        div[data-role=fieldcontain] { padding: 0 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="name">Name: </label>
                <input id="name" data-clear-btn="true" data-mini="true">
            </div>
            <div id="buttonContainer">
                <button id="enable">Enable</button>
                <button id="disable">Disable</button>
            </div>
        </form>
    </div>
</body>
</html>

我添加了按钮来改变input元素的状态,你可以看到一个被禁用的元素是如何出现在图 31-4 中的。

9781430263883_Fig31-04.jpg

图 31-4 。禁用 textinput widget

使用 Textinput 小部件事件

textinput小部件只定义了create事件,当小部件应用于一个元素时会触发该事件。

使用滑块和范围滑块部件

滑块小部件应用于input元素,其type属性设置为rangeinput元素的值用于设置标尺上滑块的初始位置,该位置由minmax属性的值定义。清单 31-4 显示了一个range input元素的简单例子。

清单 31-4 。使用滑块小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        div[data-role=fieldcontain] { padding: 0 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="quantity">Quantity: </label>
                <input id="quantity" type="range" value="10" min="1" max="20">
            </div>
        </form>
    </div>
</body>
</html>

image 提示type属性的range值是在 HTML5 中添加的,作为对input元素的更广泛增强的一部分。欲知详情,请参阅我的书《HTML5 权威指南》,这本书也是由 Apress 出版的。

我定义了一个类型属性设置为rangeinput元素,指定初始值为 10,取值范围为 1 到 20。在图 31-5 的中,你可以看到 jQuery Mobile 是如何用 slider 小部件增强这个元素的。

9781430263883_Fig31-05.jpg

图 31-5 。使用滑块小部件

您还可以通过将两个input元素配对来创建一个范围滑块 ,允许用户在单个小部件中选择一个上限值和下限值。在清单 31-5 中,你可以看到我是如何创建一个范围滑块的。

清单 31-5 。使用范围滑块小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        div[data-role=fieldcontain] { padding: 0 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <div data-role="rangeslider">
                    <label for="quantityLow">Quantity: </label>
                    <input id="quantityLow" type="range" value="10" min="1" max="20">
                    <input id="quantityHigh" type="range" value="15" min="1" max="20">
                </div>
            </div>
        </form>
    </div>
</body>
</html>

input元素包含在一个div元素中,该元素的data-role属性被设置为rangesliderinput元素的minmax值必须相同,并且value属性值用于设置用于选择范围的手柄的初始位置,如图图 31-6 所示。

9781430263883_Fig31-06.jpg

图 31-6 。使用范围滑块小部件

image 提示范围滑块部件不会改变表单数据发送到服务器的方式。将为两个input元素发送单独的值。

配置 滑块和范围滑块小部件

滑块和范围滑块部件支持相同的数据属性和配置设置,如表 31-4 所述。

表 31-4 。滑块和范围滑块部件的属性和配置设置

数据属性 环境 描述
data-highlight highlight 当设置为true时,代表所选值的滑块轨道部分高亮显示。默认值为false
data-mini mini 当设置为true时,微件以更紧凑的形式绘制。默认为false
data-track-theme trackTheme 指定滑块轨道部分的主题。

image 提示设置选项时,使用slider方法配置滑块控件,使用rangeslider方法配置范围滑块控件。

您已经在前面的小部件中看到了data-mini属性的效果,所以我在这里不再重复描述。我通常将data-highlight属性设置为true,因为我认为这使得滑块的目的——尤其是范围滑块——更加明显。在清单 31-6 中,您可以看到我是如何在范围滑块的div元素上设置属性的。

清单 31-6 。在范围滑块小部件上使用数据高亮属性

...
<form method="get">
    <div data-role="fieldcontain">
        <div data-role="rangeslider" data-highlight="false">
            <label for="quantityLow">Quantity: </label>
            <input id="quantityLow" type="range" value="10" min="1" max="20">
            <input id="quantityHigh" type="range" value="15" min="1" max="20">
        </div>
    </div>
</form>
...

在图 31-7 中,您可以看到高亮显示启用和未启用的范围滑块。

9781430263883_Fig31-07.jpg

图 31-7 。数据突出显示属性的效果

您可以通过将data-track-theme属性与全局可用的data-theme属性相结合来进一步强调滑块。data-theme属性影响滑块手柄,而data-track-theme影响滑块轨迹。在清单 31-7 中,我将前一个例子中的两个属性都应用到了范围滑块上。

清单 31-7 。为滑块手柄和轨道使用主题

...
<form method="get">
    <div data-role="fieldcontain">
        <div data-role="rangeslider" data-highlight="true"
            data-theme="b" data-track-theme="a">
            <label for="quantityLow">Quantity: </label>
            <input id="quantityLow" type="range" value="10" min="1" max="20">
            <input id="quantityHigh" type="range" value="15" min="1" max="20">
        </div>
    </div>
</form>
...

你可以在图 31-8 中看到主题的效果。

9781430263883_Fig31-08.jpg

图 31-8 。将主题应用到范围滑块及其轨道

使用滑块和范围滑块方法

滑块和范围滑块部件定义了表 31-5 中所示的方法。

表 31-5 。滑块和范围滑块方法

滑块方法 范围滑块方法 描述
slider("disable") rangeslider("disable") 禁用小部件,防止用户更改滑块值。
slider("enable") rangeslider("enable") 启用小部件,允许用户更改滑块值。
slider("refresh") rangeslider("refresh") 更新小部件以反映底层 HTML 元素的变化。

refresh方法允许您更改底层input元素的属性值,并让这些更改反映在小部件中,如清单 31-8 所示。

清单 31-8 。使用刷新方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("button").tap(function (e) {
                var currentMax = Number($("#quantityLow").attr("max"));
                $("#quantityLow, #quantityHigh").attr("max", currentMax - 1);
                $("#slider").rangeslider("refresh");
                e.preventDefault();
            });  
        });  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        div[data-role=fieldcontain] { padding: 0 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <div id="slider" data-role="rangeslider" data-highlight="true"
                        data-theme="b" data-track-theme="a">
                    <label for="quantityLow">Quantity: </label>
                    <input id="quantityLow" type="range" value="10" min="1" max="20">
                    <input id="quantityHigh" type="range" value="15" min="1" max="20">
                </div>
                <div id="buttonContainer">
                    <button>Change Range</button>
                </div>
            </div>
        </form>
    </div>
</body>
</html>

在这个例子中,我使用一个按钮来减少支撑滑块的input元素的max属性的值,并调用refresh方法,以便更新小部件来反映变化。

使用滑块事件

滑块和范围滑块小部件定义不同的事件集。在表 31-6 中,我已经描述了滑块控件可用的事件。

表 31-6 。滑块事件

事件 描述
create 创建 slider 小工具时触发。
start 在开始与滑块进行任何交互时触发,包括轻按小部件(以设定特定值)或拖动手柄时。
stop 在与滑块的任何交互结束时触发。

在清单 31-9 的中,我使用了startstop事件来更新span元素的内容。

清单 31-9 。使用滑块小部件事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("#quantity").slider({
                start: function () {
                    $("#message").text("Sliding");
                },  
                stop: function () {
                    $("#message").text(quantity.value);
                }  
            });  
        });  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        div[data-role=fieldcontain] { padding: 0 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="quantity">Quantity: </label>
                <input id="quantity" type="range" value="10" min="1" max="20">
            </div>
            Value: <span id="message">Ready</span>
        </form>
    </div>
</body>
</html>

当我接收到start事件时,我改变了span元素的内容,以指示滑块值正在改变。当我接收到stop事件时,我更新了span元素来显示已经被选中的值——注意,我是直接从input元素获取值的,而不是通过 slider 小部件。

使用范围滑块事件

在表 31-7 中,我已经描述了由范围滑块小部件定义的事件。

表 31-7 。范围滑块事件

事件 描述
create 创建范围滑块小工具时触发。
normalize 当小部件必须规范化input元素中的值时触发,这发生在用户拖动一个句柄经过另一个句柄时。

在我写这篇文章时,当使用rangeslider方法时,范围滑块小部件的事件处理工作不一致。相反,必须使用bind方法创建事件处理程序,您可以在清单 31-10 中看到它的使用。

清单 31-10 。使用范围滑块小部件事件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("#slider").bind("rangeslidernormalize", function () {
                alert("Normalized!");
            });  
        });  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        div[data-role=fieldcontain] { padding: 0 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div id="slider" data-role="rangeslider" data-highlight="true"
                    data-theme="b" data-track-theme="a">
                <label for="quantityLow">Quantity: </label>
                <input id="quantityLow" type="range" value="10" min="1" max="20">
                <input id="quantityHigh" type="range" value="15" min="1" max="20">
            </div>
        </form>
    </div>
</body>
</html>

我必须使用名称rangeslidernormalize来接收事件,这是必须使用bind方法的产物。在这个例子中,当我得到normalize事件时,我调用了alert函数——尤其是因为我还没有发现这个事件在真实世界中有用的情况。

使用选择菜单小部件

jQuery Mobile 为您提供了两种处理select元素的方法。第一种是使用 selectmenu 小部件,它为用户提供了一个带有下拉按钮的 select 元素的风格化版本。selectmenu 小部件是应用于选择元素 的默认小部件,如清单 31-11 中的所示。

清单 31-11 。包含选择元素的页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="name">Name: </label>
                <input id="name" placeholder="Your Name">
            </div>
            <div data-role="fieldcontain">
                <label for="speed"><span>Speed: </span></label>
                <select id="speed" name="speed">
                    <option value="vfast">Very Fast</option>
                    <option value="fast">Fast</option>
                    <option value="normal" selected>Normal</option>
                    <option value="slow">Slow</option>
                </select>
            </div>
            <div id="buttonContainer">
                <input type="submit" data-inline="true" value="Submit"/>
            </div>
        </form>
    </div>
</body>
</html>

你可以在图 31-9 中看到 jQuery Mobile 是如何增强select元素的。Opera Mobile Emulator 不能正确显示 selectmenu 小部件,所以我使用 BrowserStack 获得了截图。

9781430263883_Fig31-09.jpg

图 31-9 。jQuery Mobile 增强的 select 元素

配置 选择菜单小工具

selectmenu 小部件定义数据属性和配置设置,如表 31-8 所示。通过selectmenu方法应用配置选项。selectmenu 小部件的配置可以分为两个部分:配置大多数时间显示的按钮,以及配置允许用户进行选择的弹出窗口。我将在接下来的小节中描述这两个配置领域。

表 31-8 。Selectmenu 小工具的属性和配置设置

数据属性 环境 描述
data-corners corners 指定显示选项列表的按钮是否用圆角绘制。默认值为true
data-divider-theme dividerTheme 指定当nativeMenu选项为false时用于optgroup元素的主题。
data-icon icon 指定将在小工具按钮上显示的图标。
data-iconpos iconPos 指定图标在小工具按钮上的位置。
data-inline inline 指定小工具将以紧凑样式绘制。
data-mini mini 指定小工具将以较小的样式绘制。
data-native-menu nativeMenu 指定是否将使用本机 selectmenu。默认为true
data-overlay-theme overlayTheme 指定当nativeMenu选项设置为false时用于select菜单弹出的主题。

配置选择菜单按钮

selectmenu 小部件用一个按钮替换了标准的 select 元素,该按钮与 jQuery Mobile 主题的其余部分相匹配。您可以使用data-cornersdata-icondata-iconposdata-inlinedata-mini属性或其对应的配置属性来配置按钮的显示方式。我已经在前面的章节中描述了所有这些配置选项,所以我不打算深入讨论它们,但是在清单 31-12 中你可以看到我是如何应用一些属性来改变按钮的显示方式的。

清单 31-12 。配置选择菜单按钮

...
<div data-role="fieldcontain">
    <label for="speed"><span>Speed: </span></label>
    <select id="speed" name="speed"
        data-iconpos="left" data-icon="gear" data-mini="true">
        <option value="vfast">Very Fast</option>
        <option value="fast">Fast</option>
        <option value="normal" selected>Normal</option>
        <option value="slow">Slow</option>
    </select>
</div>
...

我已经改变了显示的图标,把它移到了按钮的左边,属性为data-icondata-iconpos,属性为data-mini指定了一个较小的按钮大小。你可以在图 31-10 中看到这些变化的效果。

9781430263883_Fig31-10.jpg

图 31-10 。配置选择菜单小部件按钮

配置弹出的选择菜单

默认情况下,当用户单击按钮进行选择时,selectmenu 小部件将显示本机浏览器菜单。这是一个明智的想法,因为它让用户能够访问浏览器提供的任何优化,以利用设备的功能。在图 31-11 中,你可以看到 iOS、Android 和 Opera 浏览器的本地菜单,当用户点击 selectmenu 小部件按钮时,这些菜单默认显示。

9781430263883_Fig31-11.jpg

图 31-11 。本地选择菜单

通过将data-native-menu属性设置为false,可以禁用原生菜单,并让 selectmenu 小部件显示一个与 jQuery Mobile 主题的其余部分相匹配的菜单。这使得用户无法从本机增强中获益,但却具有提供一致性的优势。在的清单 31-13 中,你可以看到我是如何使用data-native-menu来禁用本地菜单和data-overlay-theme属性来设置弹出窗口的主题的。

清单 31-13 。配置选择菜单弹出菜单

...
<div data-role="fieldcontain">
    <label for="speed"><span>Speed: </span></label>
    <select id="speed" name="speed" data-native-menu="false" data-overlay-theme="e">
        <option value="vfast">Very Fast</option>
        <option value="fast">Fast</option>
        <option value="normal" selected>Normal</option>
        <option value="slow">Slow</option>
    </select>
</div>
...

你可以在图 31-12 中看到这些变化的效果。

9781430263883_Fig31-12.jpg

图 31-12 。禁用本地选择菜单

指定占位符

通过将data-placeholder属性的值设置为true,可以将option元素用作select元素的占位符。当select元素最初显示但不在用户选择的选项列表中时,会显示占位符。清单 31-14 展示了data-placeholder属性的使用。

清单 31-14 。使用数据占位符属性

...
<div data-role="fieldcontain">
    <label for="speed"><span>Speed: </span></label>
    <select id="speed" name="speed" data-native-menu="false" data-overlay-theme="e">
        <option value="placeholder" data-placeholder="true">Select a Speed</option>
        <option value="vfast">Very Fast</option>
        <option value="fast">Fast</option>
        <option value="normal">Normal</option>
        <option value="slow">Slow</option>
    </select>
</div>
...

你可以在图 31-13 中看到效果。我通常喜欢在select菜单中使用占位符,但这是一种特别有用的技术,可以在纵向布局中隐藏label元素时为用户提供上下文。

9781430263883_Fig31-13.jpg

图 31-13 。指定占位符元素

使用 Selectmenu 方法

selectmenu 小部件定义了表 31-9 中所示的方法。

表 31-9 。selectmenu 小工具的方法

方法 描述
selectmenu("open") 打开 selectmenu 小部件以显示弹出窗口。
selectmenu("close") 关闭弹出窗口。
selectmenu("disable") 禁用小部件,以便无法选择值。
selectmenu("enable") 启用小部件,以便可以选择值。
selectmenu("refresh") 刷新小部件以合并底层select元素中的更改。

清单 31-15 显示了如何使用按钮来控制选择菜单。

清单 31-15 。以编程方式控制 Selectmenu

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function () {
            $("button").bind("tap", function (e) {
                e.preventDefault();
                if (this.id == "open") {
                    $("#speed").selectmenu("open");
                    setTimeout(function () {
                        $("#speed").selectmenu("close")
                    }, 3000);
                } else {
                    $("#speed").selectmenu(this.id)
                }  
            });  
        })  
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        [data-role=fieldcontain], .ui-grid-b { margin: 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div class="ui-grid-b">
                <div class="ui-block-a">
                    <button id="open">Open</button>
                </div>
                <div class="ui-block-b">
                    <button id="enable">Enable</button>
                </div>
                <div class="ui-block-c">
                    <button id="disable">Disable</button>
                </div>
            </div>
            <div data-role="fieldcontain">
                <label for="speed"><span>Speed: </span></label>
                <select id="speed" name="speed"
                        data-native-menu="false" data-overlay-theme="e">
                    <option value="placeholder"
                        data-placeholder="true">Select a Speed</option>
                    <option value="vfast">Very Fast</option>
                    <option value="fast">Fast</option>
                    <option value="normal">Normal</option>
                    <option value="slow">Slow</option>
                </select>
            </div>
        </form>
    </div>
</body>
</html>

我定义了三个按钮来调用openenabledisable方法。当我调用open方法时,我也使用setTimeout函数在三秒后调用close方法。

使用选择菜单事件

selectmenu 小部件只定义了create事件,当小部件应用于底层元素时会触发该事件。

使用翻转开关

如果一个select元素只包含两个option元素,您可以选择创建一个翻转开关来代替常规的 selectmenu 小部件。您可以通过将data-role属性应用到值为sliderselect元素来创建一个翻转开关,如清单 31-16 所示。

清单 31-16 。创建翻转开关

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
        [data-role=fieldcontain] { margin: 10px; text-align: center }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="speed"><span>Speed: </span></label>
                <select id="speed" name="speed" data-role="slider">
                    <option value="fast">Fast</option>
                    <option value="slow">Slow</option>
                </select>
            </div> **<div data-role="fieldcontain">**
                **<label for="size"><span>Size: </span></label>**
                **<select id="size" name="size" data-role="slider">**
                    **<option value="large">Large</option>**
                    **<option value="small" selected>Small</option>**
                **</select>**
            **</div>**
            `<div id="buttonContainer">`
                `<input type="submit" data-inline="true" value="Submit"/>`
            `</div>`
        `</form>`
    `</div>`
`</body>`
`</html>`

`本例中有两个翻转开关。你可以在图 31-14 中看到它们是如何在浏览器中显示的。用户可以通过点击或点击暴露的值,或者通过将滑块拖动到位来更改设置。

9781430263883_Fig31-14.jpg

图 31-14 。使用翻转开关

使用复选框单选小部件

checkboxradio 小部件自动应用于type属性被设置为checkboxradioinput元素。

创建复选框

创建复选框最简单的方法是定义一个类型为checkboxinput元素,后跟一个label元素,如清单 31-17 所示。

清单 31-17 。创建简单的复选框

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
        form { margin: 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="name">Name: </label>
                <input id="name" placeholder="Your Name">
            </div>

            <input type="checkbox" name="check" id="check"/>
            <label for="check">I agree</label>

            <div id="buttonContainer">
                <input type="submit" data-inline="true" value="Submit"/>
            </div>
        </form>
    </div>
</body>
</html>

你可以在图 31-15 的中看到这个复选框。我在图中显示了复选框的选中和未选中状态。

9781430263883_Fig31-15.jpg

图 31-15 。jQuery Mobile 复选框

将标签应用到复选框中

默认情况下,复选框跨越其父元素的整个宽度,这意味着在这种情况下,复选框是屏幕的整个宽度。如果你想让复选框适合布局,使它匹配它上面的 textinput 小部件,你需要使用一个特殊的元素结构,如清单 31-18 所示。

清单 31-18 。更改复选框的布局

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
        form { margin: 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="name">Name: </label>
                <input id="name" placeholder="Your Name">
            </div>

            <div data-role="fieldcontain">
                <fieldset data-role="controlgroup">
                    <legend>Terms&Conditions:</legend>
                    <input type="checkbox" name="check" id="check"/>
                    <label for="check">I agree</label>
                </fieldset>
            </div>

            <div id="buttonContainer">
                <input type="submit" data-inline="true" value="Submit"/>
            </div>
        </form>
    </div>
</body>
</html>

现在应该对外层元素很熟悉了——一个属性设置为fieldcontaindiv元素。jQuery Mobile 面临的问题是,已经有一个label元素与input元素相关联,因此您必须采用一个替代路径来为 jQuery Mobile 提供它需要的信息。您可以通过添加一个fieldset元素(其data-role被设置为controlgroup)并在input之前添加一个legend元素(包含您想要显示的文本)来实现这一点。你可以在图 31-16 中看到效果。

9781430263883_Fig31-16.jpg

图 31-16 。更改复选框的布局

分组 复选框

您可以使用一个带有controlgroupdata-rolefieldset元素将多个复选框组合在一起。清单 31-19 包含了一个演示。

清单 31-19 。将复选框分组在一起

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
        form { margin: 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="name">Name: </label>
                <input id="name" placeholder="Your Name">
            </div>

            <div data-role="fieldcontain">
                <fieldset data-role="controlgroup">
                    <legend>Choose Your Flowers:</legend>
                    <input type="checkbox" name="roses" id="roses"/>
                    <label for="roses">Roses</label>
                    <input type="checkbox" name="orchids" id="orchids"/>
                    <label for="orchids">Orchids</label>
                    <input type="checkbox" name="asters" id="asters"/>
                    <label for="asters">Asters</label>
                </fieldset>
            </div>

            <div data-role="fieldcontain">
                <fieldset data-role="controlgroup" data-type="horizontal">
                    <legend>Font:</legend>
                    <input type="checkbox" name="bold" id="bold"/>
                    <label for="bold"><b>b</b></label>
                    <input type="checkbox" name="italic" id="italic"/>
                    <label for="italic"><em>i</em></label>
                    <input type="checkbox" name="underline" id="underline"/>
                    <label for="underline"><u>u</u></label>
                </fieldset>
            </div>

            <div id="buttonContainer">
                <input type="submit" data-inline="true" value="Submit"/>
            </div>
        </form>
    </div>
</body>
</html>

本例中有两组复选框。第一组是垂直布局,这是默认方向。jQuery Mobile 改变了小部件的样式,使得各个input元素之间没有空格,只有块的外部拐角是圆形的。对于第二组,我将data-type属性设置为horizontal,这将改变布局的方向并使 jQuery Mobile 隐藏复选框,从而创建一组可以开关的按钮。你可以在图 31-17 中看到结果。

9781430263883_Fig31-17.jpg

图 31-17 。分组复选框

创建和格式化单选按钮

创建单选按钮并对其进行格式化的方式与格式化复选框的方式非常相似。清单 31-20 包含了一个例子。

清单 31-20 。创建一组单选按钮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        #buttonContainer {text-align: center}
        form { margin: 10px; }
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <form method="get">
            <div data-role="fieldcontain">
                <label for="name">Name: </label>
                <input id="name" placeholder="Your Name">
            </div>

            <div data-role="fieldcontain">
                <fieldset data-role="controlgroup">
                    <legend>Choose Your Flowers:</legend>
                    <input type="radio" name="flowers" id="roses"/>
                    <label for="roses">Roses</label>
                    <input type="radio" name="flowers" id="orchids"/>
                    <label for="orchids">Orchids</label>
                    <input type="radio" name="flowers" id="asters"/>
                    <label for="asters">Asters</label>
                </fieldset>
            </div>

            <div data-role="fieldcontain">
                <fieldset data-role="controlgroup" data-type="horizontal">
                    <legend>Choose Your Flowers:</legend>
                    <input type="radio" name="flowers" id="roses"/>
                    <label for="roses">Roses</label>
                    <input type="radio" name="flowers" id="orchids"/>
                    <label for="orchids">Orchids</label>
                    <input type="radio" name="flowers" id="asters"/>
                    <label for="asters">Asters</label>
                </fieldset>
            </div>

            <div id="buttonContainer">
                <input type="submit" data-inline="true" value="Submit"/>
            </div>
        </form>
    </div>
</body>
</html>

我再次创建了水平和垂直组,你可以在图 31-18 中看到它们是如何在浏览器中显示的。

9781430263883_Fig31-18.jpg

图 31-18 。创建单选按钮组

配置 复选框单选小部件

checkboxradio 小部件只支持一个配置选项,我已经在表 31-10 中描述过了。

表 31-10 。checkboxradio 小部件的属性和配置设置

数据属性 环境 描述
data-mini mini 当设置为true时,微件以紧凑形式创建。

使用复选框单选方法

checkboxradio 小部件定义了表 31-11 中所示的方法。

表 31-11 。checkboxradio 小部件的方法

方法 描述
checkboxradio("disable") 禁用小部件,以便无法选择值。
checkboxradio("enable") 启用小部件,以便可以选择值。
checkboxradio("refresh") 刷新小部件以合并基础元素中的更改。

使用复选框单选事件

checkboxradio 小部件只定义了create事件,该事件在小部件应用于底层元素时被触发。

摘要

在这一章中,我展示了 jQuery Mobile 如何增强表单元素,使它们与更广泛的触控样式保持一致。您不需要采取任何特殊的操作来提交表单,这是使用 Ajax 自动完成的,因此 jQuery Mobile 可以平稳地管理到服务器返回的页面的转换。您可以依靠 jQuery Mobile 来自动增强表单元素,但是有一些很好的理由来应用一些额外的元素和data-role属性,尤其是在处理select元素的时候。在第三十二章中,我继续描述 jQuery Mobile 小部件,转向 listview 和 panel 小部件。`

三十二、使用列表和面板

在这一章中,我描述了 jQuery Mobile 列表和面板小部件。列表是构建移动 web 应用的一个重要工具,它们通常为 web 应用的不同功能区域提供简单而明显的导航。列表的美妙之处在于它们很紧凑,即使单个列表项大到可以通过触摸来选择。它们也非常容易被用户理解。只需在列表项的右边缘放置一个箭头图标(jQuery Mobile 默认这样做),就可以让大多数用户明白选择该项将导致某种选择或导航的发生。

面板是一个通用的小部件,它将内容带入窗口左侧或右侧的上下文中。面板可用于显示任何内容,但最常用于整个应用中持久的设置或导航功能。表 32-1 对本章进行了总结。

表 32-1 。章节总结

问题 解决办法 列表
创建一个列表。 定义一个包含一个或多个li元素并且将data-role属性设置为listviewulol元素。li元素的内容应该是链接。 one
创建插入列表。 data-inset属性设置为true Two
创建一个列表,其项目由两个不同的部分组成。 向每个li元素添加第二个链接。 three
允许用户过滤列表内容。 data-filter属性设置为true 4, 5
在列表中添加分隔线。 在单个li元素上将data-role属性设置为list-divider six
向列表项添加计数气泡。 使用ui-li-count类。 seven
使用不同的文本强调。 使用h1 - h6p元素。 eight
向列表项添加旁白。 使用ui-li-aside类。 nine
创建一个面板。 将面板相关页面中包含的div元素的data-role属性设置为panel Ten
设置面板的位置和显示样式。 使用data-displaydata-position属性。 Eleven
指定如何解散面板。 使用data-swipe-closedata-dismissable属性。 Twelve

使用 ListView 部件

jQuery Mobile 通过 listview 小部件为处理列表提供了灵活的支持。清单 32-1 显示了一个链接到同一文档中的 jQuery Mobile 页面的基本列表。每个页面描述一种不同的花,列表为用户提供了导航到这些页面的机制。

清单 32-1 。基本列表

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>

        <ul data-role="listview">
            <li><a href="#roses">Roses</a></li>
            <li><a href="#orchids">Orchids</a></li>
            <li><a href="#asters">Asters</a></li>
        </ul>
    </div>

  <div id="roses" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Roses</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="rose.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                A rose is a woody perennial within the family Rosaceae.
                They form a group of erect shrubs, and climbing or trailing plants.
                <div><b>Price: $4.99</b></div>
            </div>
        </div>
    </div>

   <div id="orchids" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Orchids</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="orchid.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The orchid family is a diverse and widespread family in the order
                Asparagales. It is one of the largest families of flowering plants.
                <div><b>Price: $10.99</b></div>
            </div>
        </div>
    </div>

   <div id="asters" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Asters</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="aster.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The name Aster comes from the Ancient Greek word meaning "star",
                referring to the shape of the flower head.
                <div><b>Price: $2.99</b></div>
            </div>
        </div>
    </div>
</body>
</html>

本文档的大部分内容都用于描述花卉。实际的列表只有几个元素,如下所示:

...
<ul data-role="listview">
    <li><a href="#roses">Roses</a></li>
    <li><a href="#orchids">Orchids</a></li>
    <li><a href="#asters">Asters</a></li>
</ul>
...

image 提示我在这个例子中使用了ul元素,但是 jQuery Mobile 以同样的方式处理编号列表(用ol元素创建)。

这是一个标准的 HTML 无编号列表,使用ul元素表示,其中包含三个li元素。为了应用 listview 小部件,我将ul元素上的data-role属性设置为listview

列表小部件的基本用途是提供导航,为此,每个li元素的内容是一个a元素,它链接到文档中的其他页面。单击或轻触单个列表项会将用户带到相应的页面。您可以在图 32-1 中看到 listview 小部件和其中一个内容页面。我在每个内容页面上添加了一个基于链接的按钮,使用标准转换(但方向相反)将用户带回列表。

9781430263883_Fig32-01.jpg

图 32-1 。一个简单的 jQuery Mobile listview 窗口小部件

配置列表视图小部件

listview 小部件支持许多数据属性和配置设置,可用于更改列表的外观和行为。这些在表 32-2 中描述,并在以下章节中演示。

表 32-2 。Listview 小部件的属性和配置设置

数据属性 环境 描述
data-count-theme countTheme 指定计数气泡的主题。
data-divider-theme dividerTheme 指定分隔线的主题。
data-filter filter 当设置为true时,listview 显示一个过滤器。
不适用的 filterCallback 指定由筛选器调用的函数。
data-filter-placeholder filterPlaceholder 指定用于过滤的占位符。
data-filter-theme filterTheme 指定筛选器搜索栏的主题。
data-header-theme headerTheme 指定嵌套标题的主题。
data-icon icon 指定标题上使用的图标。
data-inset inset 当设置为true时,listview 以适合嵌套列表的样式绘制。
data-split-icon splitIcon 指定拆分列表的图标。
data-split-theme splitTheme 指定拆分列表的主题。

创建插入列表

列表的默认布局是填充容器元素的宽度并具有方形的角,这与其他 jQuery Mobile 小部件的风格不匹配。为了使样式一致,您可以创建一个嵌入列表,该列表具有圆角,可用于不接触屏幕边缘的元素。您可以通过将值为truedata-inset属性应用到ulol元素来创建一个插入列表,如清单 32-2 所示。

清单 32-2 。创建插入列表

...
<div id="page1" data-role="page" data-theme="b">
    <div data-role="header">
       <h1>Jacqui's Shop</h1>
    </div>

    <div id="container" style="padding: 20px">
        <ul data-role="listview"data-inset=true>
            <li><a href="#roses">Roses</a></li>
            <li><a href="#orchids">Orchids</a></li>
            <li><a href="#asters">Asters</a></li>
        </ul>
    </div>
</div>
...

在这个例子中,我将ul元素放在了div元素中。我使用 CSS padding设置从父元素的边缘插入列表,并使用data-inset属性改变列表的样式。你可以在图 32-2 中看到结果。

9781430263883_Fig32-02.jpg

图 32-2 。创建插入列表

创建拆分列表

当列表中的每个项目可以执行两个操作时,拆分列表非常有用。列表项被分成两个部分,点击或轻敲列表项的每个部分会导致不同的操作。清单 32-3 显示了一个分割列表,允许用户获得关于一朵花的信息,或者简单地将它添加到他们的购物篮中。

清单 32-3 。使用拆分列表

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center; margin: 20px}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>

        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true>
                <li><a href="#basket" class="buy" id="rose">Roses</a>
                    <a href="#roses">Roses</a></li>
                <li><a href="#basket" class="buy" id="orchid">Orchids</a>
                    <a href="#orchids">Orchids</a>  </li>
                <li><a href="#basket" class="buy" id="aster">Asters</a>
                    <a href="#asters">Asters</a>  </li>
            </ul>
        </div>
    </div>

    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            Basket will go here
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
        </div>
    </div>

    <div id="roses" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Roses</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="rose.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                A rose is a woody perennial within the family Rosaceae.
                They form a group of erect shrubs, and climbing or trailing plants.
                <div><b>Price: $4.99</b></div>
            </div>
        </div>
    </div>

   <div id="orchids" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Orchids</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="orchid.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The orchid family is a diverse and widespread family in the order
                Asparagales. It is one of the largest families of flowering plants.
                <div><b>Price: $10.99</b></div>
            </div>
        </div>
    </div>

   <div id="asters" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Asters</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="aster.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The name Aster comes from the Ancient Greek word meaning "star",
                referring to the shape of the flower head.
                <div><b>Price: $2.99</b></div>
            </div>
        </div>
    </div>
</body>
</html>

要创建一个分割列表,向li元素添加第二个a元素。jQuery Mobile 将每个列表项一分为二,并在各部分之间插入一个垂直分隔线。单击或轻击项目的左边部分导航到第一个a元素的目标,单击或轻击右边部分导航到第二个a元素。你可以在图 32-3 中看到列表项是如何呈现的。

9781430263883_Fig32-03.jpg

图 32-3 。创建拆分列表

在这个例子中,我将列表项的所有左边部分设置为指向我添加到名为basket的文档中的新页面。我将在第三十三章的中回到这个例子,并扩展它来放置一个简单的购物篮。对于这个例子,basket页面只是一个占位符。

image 提示 jQuery Mobile 默认使用箭头图标作为拆分按钮。您可以通过将data-split-icon属性应用到ulol元素来改变这一点,指定您想要的图标的名称。第三十章包含了可用图标的列表。

过滤列表

listview 小部件提供了一种过滤列表内容的机制,这是通过将值为truedata-filter属性应用到ulol元素来实现的,如清单 32-4 所示。

清单 32-4 。使用列表过滤

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>

        <div data-role="content">
            <ul data-role="listview" data-inset=truedata-filter=true>
                <li><a href="#roses">Roses</a></li>
                <li><a href="#orchids">Orchids</a></li>
                <li><a href="#asters">Asters</a></li>
            </ul>
        </div>
    </div>

  <div id="roses" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Roses</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="rose.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                A rose is a woody perennial within the family Rosaceae.
                They form a group of erect shrubs, and climbing or trailing plants.
                <div><b>Price: $4.99</b></div>
            </div>
        </div>
    </div>

   <div id="orchids" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Orchids</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="orchid.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The orchid family is a diverse and widespread family in the order
                Asparagales. It is one of the largest families of flowering plants.
                <div><b>Price: $10.99</b></div>
            </div>
        </div>
    </div>

   <div id="asters" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Asters</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="aster.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The name Aster comes from the Ancient Greek word meaning "star",
                referring to the shape of the flower head.
                <div><b>Price: $2.99</b></div>
            </div>
        </div>
    </div>
</body>
</html>

在图 32-4 中可以看到,jQuery Mobile 在列表上方添加了一个搜索栏。当用户在搜索栏中输入字符时,jQuery Mobile 会从列表中删除不包含该字符序列的所有项目。(默认情况下,只有在筛选器中输入了至少两个字符后,才会执行筛选。)

9781430263883_Fig32-04.jpg

图 32-4 。启用列表过滤

image 注意过滤列表的能力是一个很棒的功能,但在小触摸屏上并不总是有用。为了支持字符输入,当用户激活诸如搜索栏的文本输入元素时,大多数移动设备显示弹出触摸键盘。在小型设备上,键盘会占据屏幕很大一部分,以至于用户不容易看到过滤器的结果。这并不意味着您不应该支持列表过滤,但是如果您针对的是小屏幕设备,提供其他导航机制是很重要的。

使用自定义过滤功能

默认筛选器匹配任何包含用户输入的字符集的列表项。这些匹配列表项文本中的任何位置,并且不区分大小写。您可以通过使用 jQuery UI 风格的方法提供一个定制的过滤函数,如清单 32-5 所示。

清单 32-5 。使用自定义列表过滤功能

...
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript">
        $(document).bind("pageinit", function () {
            $("ul").listview("option", "filterCallback", function (listItem, filter) {
                var pattern = new RegExp("^" + filter, "i");
                return !pattern.test(listItem)
            })
        })
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
    </style>
</head>
...

您可以通过调用option方法来设置自定义函数,并将该函数用作filterCallback设置的值。该函数的参数是列表项中的文本和用户输入的筛选器。列表中的每一项都会调用一次该函数,如果返回true,调用该函数的项将被隐藏。在本例中,我使用正则表达式将匹配限制在以过滤器文本开头的列表项。你可以在图 32-5 的中看到结果,在过滤框中键入字母R只匹配Roses项。

9781430263883_Fig32-05.jpg

图 32-5 。使用自定义过滤器

添加分隔符

listview 小部件可以在列表项之间添加分隔线。这些分隔符由元素指定,这些元素的data-role属性被设置为list-divider,,它们可以通过应用于定义列表的ulol元素的data-divider-theme属性来设置样式。

列表分隔线有助于为长列表或复杂列表提供结构,而不会改变用户在列表中导航的方式。在清单 32-6 中,你可以看到我是如何添加一些分隔线元素并将data-divider-theme属性应用到例子中的。

清单 32-6 。使用列表分隔符

...
<div id="page1" data-role="page" data-theme="b">
    <div data-role="header">
       <h1>Jacqui's Shop</h1>
    </div>

    <div data-role="content">
        <ul data-role="listview" data-inset=truedata-theme="c"
            data-divider-theme="b">

            <li data-role="list-divider">A</li>
            <li><a href="#asters">Asters</a></li>
            <li data-role="list-divider">C</li>
            <li><a href="document2.html">Carnations</a></li>
            <li data-role="list-divider">D</li>
            <li><a href="document2.html">Daffodils</a></li>
            <li data-role="list-divider">L</li>
            <li><a href="document2.html">Lilies</a></li>
            <li data-role="list-divider">O</li>
            <li><a href="#orchids">Orchids</a></li>
            <li data-role="list-divider">P</li>
            <li><a href="document2.html">Peonies</a></li>
            <li><a href="document2.html">Primulas</a></li>
            <li data-role="list-divider">R</li>
            <li><a href="#roses">Roses</a></li>
            <li data-role="list-divider">S</li>
            <li><a href="document2.html">Snowdrops</a></li>
        </ul>
    </div>
</div>
...

你可以在图 32-6 中看到分隔器产生的效果。

9781430263883_Fig32-06.jpg

图 32-6 。向列表中添加分隔符

image 提示如果你想让一个元素有不同的外观,你可以将data-theme属性直接应用于单个列表项。

使用基于约定的配置

一些配置选项是通过约定而不是配置来处理的。当你看分割列表时,你已经看到了这样的例子。如果将第二个a元素添加到一个li元素的内容中,jQuery Mobile 会自动创建一个拆分列表项。您不必应用数据属性来创建这种效果——它就这样发生了。在这一节中,我将向您展示三种可以用来格式化列表项的不同约定:计数气泡、文本强调和旁白。

添加计数气泡

您可以向列表项添加一个小的数字指示器。这些被称为计数气泡,当列表项代表某种类别,并且您想要提供关于有多少可用的信息时,它们会很有用。例如,如果您的列表项代表电子邮件文件夹,您可以使用计数气泡来指示每个文件夹中有多少封邮件。您还可以使用计数气泡来显示电子商务应用中的库存商品数量。

虽然这种效果通常用于表示数值,但是您可以显示任何您喜欢的信息。价值的意义需要是不言自明的,因为你没有空间向用户提供解释——只有价值。

通过向一个li元素的内容添加一个额外的子元素来创建一个计数气泡。这个子元素必须包含值并被分配给ui-li-count类。你可以看到在清单 32-7 中定义的计数气泡的例子,包括一个使用非数值的例子。

清单 32-7 。将计数气泡添加到列表项目

...
<div id="page1" data-role="page" data-theme="b">
    <div data-role="header">
       <h1>Jacqui's Shop</h1>
    </div>

    <div data-role="content">
      <ul data-role="listview" data-inset=true data-filter=true>
          <li><a href="#roses">Roses<div class="ui-li-count">23</div></a></li>
          <li><div class="ui-li-count">7</div><a href="#orchids">Orchids</a></li>
          <li><a href="#asters">Asters</a><div class="ui-li-count">Pink</div></li>
      </ul>
    </div>
</div>
...

注意,您可以将子元素放在li元素中的任何位置。它不一定是最后一个元素(尽管这是一个常见的约定)。你可以在图 32-7 中看到计数气泡是如何显示的。

9781430263883_Fig32-07.jpg

图 32-7 。使用反气泡

添加文本强调

当您使用包装在标题元素(h1h6元素)而不是p元素(表示段落)中的内容时,listview 小部件将应用不同的强调级别。这允许你创建一个包含标题和一些支持细节文本的列表项,如清单 32-8 所示。

清单 32-8 。添加文本强调

...
<div id="page1" data-role="page" data-theme="b">
    <div data-role="header">
       <h1>Jacqui's Shop</h1>
    </div>

    <div data-role="content">
      <ul data-role="listview" data-inset=true data-filter=true>
          <li>
            <a href="#roses"><h1>Roses</h1>
                <p>A rose is a woody perennial within the family Rosaceae.</p>
                <div class="ui-li-count">$4.99</div></a>
          </li>
          <li><div class="ui-li-count">7</div><a href="#orchids">Orchids</a></li>
          <li><a href="#asters">Asters</a><div class="ui-li-count">Pink</div></li>
      </ul>
    </div>
</div>
...

在这个例子中,我使用了h1元素来表示产品的名称,使用了p元素来表示详细信息。我包括了一个计数气泡,表明项目的价格。(价格非常适合用来计算泡沫,因为货币符号为数值提供了直接的含义。)你可以在图 32-8 中看到效果。

9781430263883_Fig32-08.jpg

图 32-8 。在列表项中使用文本强调

添加旁白

撇开不谈是使用计数气泡的替代方法。要创建旁白,您需要向包含您想要显示的信息的li元素添加一个子元素,并将其分配给ui-li-aside类。你可以在清单 32-9 中看到旁白的使用。

清单 32-9 。在列表项中创建旁白

...
<div id="page1" data-role="page" data-theme="b">
    <div data-role="header">
       <h1>Jacqui's Shop</h1>
    </div>

    <div data-role="content">
      <ul data-role="listview" data-inset=true data-filter=true>
          <li>
            <a href="#roses">
                <h1>Roses</h1>
                <p>A rose is a woody perennial within the family Rosaceae.</p>
                <p class="ui-li-aside">(Pink) <strong>$4.99</strong></p>
            </a></li>
          <li><div class="ui-li-count">7</div><a href="#orchids">Orchids</a></li>
          <li><a href="#asters">Asters</a><div class="ui-li-count">Pink</div></li>
      </ul>
    </div>
</div>
...

在图 32-9 中,您可以看到玫瑰项目的旁白显示样式。

9781430263883_Fig32-09.jpg

图 32-9 。使用旁白

使用 Listview 方法

listview 小部件定义了表 32-3 中所示的两种方法。

表 32-3 。Listview 方法

方法 描述
listview("refresh") 更新 listview 小部件以反映基础元素的变化。

使用 Listview 事件

listview小部件只定义了create事件,当小部件应用于一个元素时会触发该事件。

使用面板小部件

面板小部件出现在当前页面的左侧或右侧——您可以使用面板来显示任何内容,但最常见的用途是提供访问导航选项和应用设置。通过将div元素的data-role属性设置为panel来创建面板,如清单 32-10 中的所示。

清单 32-10 。创建面板小部件

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style>
        .buttonContainer { text-align: center; }
    </style>
</head>
<body>

    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>

        <div data-role="content" class="buttonContainer">
            <a data-role="button" data-inline="true" href="#panel">Open Panel</a>
        </div>

        <div id="panel" data-role="panel" data-theme="a">
            <div data-role="panel-content">
                <h3>Simple Panel</h3>
                <p>This is the the panel</p>
                <button data-rel="close" data-inline="true">Close</button>
            </div>
        </div>
    </div>
</body>
</html>

面板小部件的元素是在显示面板的页面中定义的。在本例中,我定义了一个包含一些简单 HTML 元素的面板,这些元素被包装在一个div元素中,我将该元素的data-role属性设置为panel-content,这确保了内容在面板中的正确定位。

本例中的主页包含一个a元素,其href元素指定了面板元素的id。单击链接——或者按钮,因为我已经将a元素上的data-role属性设置为 button——打开面板。在图 32-10 中可以看到效果。

9781430263883_Fig32-10.jpg

图 32-10 。使用弹出窗口部件

这是一个你需要亲身体验才能正确理解的例子,但是点击打开面板按钮会将主页滑动到右边来显示面板。我可以通过点击面板中的Close按钮(我通过将data-rel属性设置为close来配置)或者点击主页上仍然可见的部分来关闭面板。

配置面板小部件

面板微件定义了表 32-4 中所示的数据属性和配置设置。

表 32-4 。面板部件的属性和配置设置

数据属性 环境 描述
data-animate animate 指定面板在打开或关闭时是否会显示动画。默认为true
data-dismissable dismissable 指定是否可以通过点击打开面板的页面来关闭面板。默认为true
data-display display 指定面板和页面之间的关系。这些值是revealpush,overlay,如下所述
data-position position 指定面板的显示位置。值为leftright。默认是left
data-position-fixed positionFixed 指定即使用户向下滚动页面,面板的内容是否仍然可见。默认为false
data-swipe-close swipeClose 指定面板是否可以通过滑动来关闭。默认为true

定位和显示面板

data-displaydata-position属性决定了面板的显示位置(窗口的leftright侧)以及它相对于打开它的页面的显示方式。data-display属性有三个值,我已经在表 32-5 中描述过了。

表 32-5 。数据显示属性的值

价值 描述
reveal 默认值:面板将页面推开。
push 调整页面大小以与面板共享空间。
overlay 面板在页面上滑动。

在清单 32-11 中,你可以看到这两种设置的效果,演示了所有显示和位置选项的排列。

清单 32-11 。定位面板

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("#pageContent button").tap(function (e) {
                $("#" + this.id + "Panel").panel({
                    display: $("input[type=radio]:checked").attr("id")
                }).panel("open");
            });
        });
    </script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>

        <div id="pageContent" data-role="content">
            <div class="ui-grid-a">
                <div class="ui-block-a"><button id="left">Left</button></div>
                <div class="ui-block-b"><button id="right">Right</button></div>
            </div>

            <div data-role="fieldcontain">
                <fieldset data-role="controlgroup" data-type="horizontal">
                    <input type="radio" name="display" id="reveal" checked="checked"/>
                    <label for="reveal">Reveal</label>
                    <input type="radio" name="display" id="push"/>
                    <label for="push">Push</label>
                    <input type="radio" name="display" id="overlay"/>
                    <label for="overlay">Overlay</label>
                </fieldset>
            </div>
        </div>

        <div id="leftPanel" data-role="panel" data-theme="a" data-position="left">
            <div data-role="panel-content">
                <h3>Left Panel</h3>
                <p>This is the the left panel</p>
                <button data-rel="close" data-inline="true">Close</button>
            </div>
        </div>

        <div id="rightPanel" data-role="panel" data-theme="a" data-position="right">
            <div data-role="panel-content">
                <h3>Right Panel</h3>
                <p>This is the the right panel</p>
                <button data-rel="close" data-inline="true">Close</button>
            </div>
        </div>
    </div>
</body>
</html>

我使用一对按钮来打开左右面板(使用open方法,我将在本章后面介绍)和一组单选按钮(如第三十章中的所述)来选择显示模式。你可以在图 32-11 中看到一些排列。

9781430263883_Fig32-11.jpg

图 32-11 。更改显示和位置选项

panel 小部件不能在窗口的左侧和右侧显示相同的元素:准备内容的过程会以某种方式调整它。正是因为这个原因,我在这个例子中使用了两个独立的面板。

解散陪审团

data-swipe-closedata-dismissable属性允许你控制用户关闭面板的方式,通过滑动手势或点击打开面板的页面。将这些属性设置为false会创建一个面板,该面板只能在用户与面板内容交互时关闭(或者通过编程使用opentoggle方法,我将在本章稍后描述)。

我对从用户手中夺走控件的控制权持谨慎态度,如果你决定使用这些属性,那么你应该确保在你的 web 应用中保持一致——让面板以不同的方式消失只会导致挫败感。在清单 32-12 中,我创建了一个面板,它会显示一段固定的时间,然后自行消失——这不是我在实际应用中推荐的,但对演示小部件功能很有用。用户不能通过点击打开面板的页面或滑动来关闭面板,但该按钮仍可用于关闭面板。

清单 32-12 。创建不能通过点击页面或滑动来消除的面板

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script>
        $(document).bind("pageinit", function () {
            $("a").tap(function (e) {
                var timeRemaining = 15;
                var intervalId = setInterval(function () {
                    $("#remaining").text(timeRemaining--);
                    if (timeRemaining == 0) {
                        $("#panel").panel("close");
                        clearInterval(intervalId);
                    }
                }, 1000);
                $("#panel").panel("open");
            });
        });
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>

        <div data-role="content" class="buttonContainer">
            <a data-role="button" data-inline="true">Open Panel</a>
        </div>

        <div id="panel" data-role="panel" data-theme="a"
                data-dismissable="false" data-swipe-close="false">
            <div data-role="panel-content">
                <h3>Simple Panel</h3>
                <p>This panel will close in
                    <span id="remaining">15</span> seconds.</p>
                <button data-rel="close" data-inline="true">Close</button>
            </div>
        </div>
    </div>
</body>
</html>

我使用openclose方法来控制面板的可见性——我将在下一节适当描述这些方法——并使用 JavaScript setInterval函数来管理倒计时,在面板打开 15 秒后关闭面板。用户可以通过使用面板中显示的button元素提前关闭面板——如果您想阻止用户关闭面板,那么您必须确保面板不包含此类元素。当底层元素发生变化时,面板小部件会反映这些变化,如图图 32-12 所示。

9781430263883_Fig32-12.jpg

图 32-12 。自动关闭面板

使用面板方法

面板微件定义了表 32-6 中所示的方法。在前面的例子中演示了openclose方法。

表 32-6 。面板方法

方法 描述
panel("open") 显示面板。
panel("close") 隐藏面板。
panel("toggle") 切换面板的可见性:显示隐藏的面板,隐藏可见的面板。

使用面板事件

面板微件定义了表 32-7 中所示的事件。我并不认为这些事件在我自己的项目中有用,因为我更喜欢处理导致面板显示或隐藏的元素中的事件。

表 32-7 。小组活动

事件 描述
create 创建小部件时触发。
beforeopen 在面板显示之前触发。
beforeclose 面板隐藏前触发。
open 面板显示后触发。
close 面板隐藏后触发。

摘要

在这一章中,我描述了 jQuery Mobile list 小部件,它是移动 web 应用的基本导航工具。我向您展示了可以创建的不同类型的列表,可以呈现给用户的不同样式的列表,以及可以用来管理单个列表项内容的配置和约定。

三十三、重构示例:第四部分

在本书这一部分的前几章,我向您介绍了 jQuery Mobile。在本章中,我将构建一个使用 jQuery Mobile 功能的更完整的例子。就其本质而言,jQuery Mobile 比 jQuery UI 简单得多,可用的设计选择也少得多。移动设备开发面临的独特问题进一步限制了 jQuery Mobile 的开发工作。

从基础开始

在第三十二章中,我展示了一个使用分割列表的例子。这个例子是本章的起点,我将用它来构建一些额外的功能。清单 33-1 显示了本章的初始示例文档。

清单 33-1 。本章的起点

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center; margin: 20px}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true>
                <li><a href="#basket" class="buy" id="rose">Roses</a>
                    <a href="#roses">Roses</a></li>
                <li><a href="#basket" class="buy" id="orchid">Orchids</a>
                    <a href="#orchids">Orchids</a>  </li>
                <li><a href="#basket" class="buy" id="aster">Asters</a>
                    <a href="#asters">Asters</a>  </li>
            </ul>
        </div>
    </div>
    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            Basket will go here
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
        </div>
    </div>
    <div id="roses" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Roses</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="rose.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                A rose is a woody perennial within the family Rosaceae.
                They form a group of erect shrubs, and climbing or trailing plants.
                <div><b>Price: $4.99</b></div>
            </div>
        </div>
    </div>

   <div id="orchids" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Orchids</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="orchid.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The orchid family is a diverse and widespread family in the order
                Asparagales. It is one of the largest families of flowering plants.
                <div><b>Price: $10.99</b></div>
            </div>
        </div>
    </div>
   <div id="asters" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Asters</h1>
        </div>
        <div>
            <div class="lcontainer">
                <img src="aster.png">
                <div><a href="#" data-rel="back" data-role="button"
                       data-inline=true data-direction="reverse">Back</a>
                </div>
            </div>
            <div class="productData">
                The name Aster comes from the Ancient Greek word meaning "star",
                referring to the shape of the flower head.
                <div><b>Price: $2.99</b></div>
            </div>
        </div>
    </div>
</body>
</html>

以编程方式插入产品

我要做的第一件事是用一些动态创建的页面替换描述每朵花的静态页面。这一改变使我有了一个更紧凑的文档,并且可以方便地添加更多的花朵供用户选择,而无需复制 HTML 元素。我将使用数据模板生成页面,我在第十二章中描述过。数据模板与核心 jQuery 库一起工作,因此也非常适合 jQuery Mobile 应用。我创建了一个名为data.json 的文件,其中包含了我需要的花的数据。清单 33-2 显示了data.json的内容。

清单 33-2 。data.json 文件的内容

[{  "name": "aster",
    "label": "Asters",
    "price": "$2.99",
    "text": "The name Aster comes from the Ancient Greek word meaning star..."
},{ "name": "carnation",
    "label": "Carnations",
    "price": "$1.99",
    "text": "Carnations require well-drained, neutral to slightly alkaline soil..."
},{ "name": "daffodil",
    "label": "Daffodils",
    "price": "$1.99",
    "text": "Daffodil is a common English name, sometimes used for all varieties..."
},{ "name": "rose",
    "label": "Roses",
    "price": "$4.99",
    "text":  "A rose is a woody perennial within the family Rosaceae. They form a..."
},{ "name": "orchid",
    "label": "Orchids",
    "price": "$10.99",
    "text": "The orchid family is a diverse and widespread family in the order..."
}]

数据描述了五种花。对于它们中的每一个,我都定义了产品名称、显示给用户的标签、单价和文本描述。

image 注意我没有在清单中展示全文描述,但是它包含在data.json文件中,该文件是本书源代码下载的一部分(可以从Apress.com获得)。

现在我有了数据,我可以将它集成到文档中。清单 33-3 展示了使用数据模板从静态页面到程序生成页面的变化。

清单 33-3 。动态添加页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
    </style>
    <script id="flowerTmpl" type="text/x-handlebars-template">
        {{#products}}
       <div id="{{name}}" data-role="page" data-theme="b">
            <div data-role="header">
               <h1>{{label}}</h1>
            </div>
            <div>
                <div class="lcontainer">
                    <img src="{{name}}.png">
                    <div><a href="#" data-rel="back" data-role="button"
                           data-inline=true data-direction="reverse">Back</a>
                    </div>
                </div>
                <div class="productData">
                    {{text}}
                    <div><b>Price: {{price}}</b></div>
                </div>
            </div>
        </div>
        {{/products}}
    </script>

    <script id="liTmpl" type="text/x-handlebars-template">
        {{#products}}
        <li>
            <a href="#basket" class="buy" id="A1">{{label}}</a>
            <a href="#{{name}}">{{label}}</a>
        </li>
        {{/products}}
    </script>

    <script type="text/javascript">
        var initComplete = false;
        $(document).bind("pageinit", function () {
            if (!initComplete) {
                $.getJSON("data.json", function (data) {
                    $("#flowerTmpl").template({ products: data })
                        .filter("*").appendTo("body");
                    $("ul").append($("#liTmpl").template({ products: data })
                        .filter("*")).listview("refresh")
                });
                initComplete = true;
            }
        })
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>
    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            Basket will go here
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
        </div>
    </div>
</body>
</html>

我移除了每朵花的页面,并使用数据模板从数据中生成我需要的内容,这些数据是我使用getJSON方法获得的(在第十四章中描述)。这一变化的关键是简单的定制 JavaScript 代码,如下所示:

..
<script type="text/javascript">
    var initComplete = false;
    $(document).bind("pageinit", function () {
        if (!initComplete) {
            $.getJSON("data.json", function (data) {
                $("#flowerTmpl").template({ products: data })
                    .filter("*").appendTo("body");
                $("ul").append($("#liTmpl").template({ products: data })
                    .filter("*")).listview("refresh")
            });
            initComplete = true;
        }
    })
</script>
...

当我获得数据时,我使用模板从数据中生成元素,并将动态生成的页面添加到文档中的body元素。我还使用一个模板来生成主鲜花列表的项目。我告诉 jQuery Mobile 我已经修改了列表的内容,这是通过调用listview小部件上的refresh方法来完成的,如下所示:

...
$("ul").append($("#liTmpl").template({ products: data })
    .filter("*")).listview("refresh");
...

数据模板很简单,使用了我在第十二章中描述的标准技术。您可以在图 33-1 中看到结果——一个列表,其项目以编程方式生成,并链接到已经以编程方式添加到文档中的页面。

9781430263883_Fig33-01.jpg

图 33-1 。以编程方式生成的列表项和页面

重用页面

我喜欢数据模板方法,因为它展示了 jQuery 如何支持如此广泛的功能,允许您将模板等功能与 jQuery Mobile 等接口工具包结合在一起。

也就是说,您可以采用一种更优雅的方法来处理每朵花的页面。您可以生成一组元素并修改它们来显示用户选择的花,而不是为您想要显示的每朵花生成一组元素。清单 33-4 展示了使这成为可能的对文档的修改。

清单 33-4 。为多个产品重复使用一个页面

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script id="liTmpl" type="text/x-handlebars-template">
        {{#products}}
        <li>
            <a href="#basket" class="buy" id="{{name}}">{{label}}</a>
            <a class="productLink" data-flower="{{name}}" href="#">{{label}}</a>
        </li>
        {{/products}}
    </script>
    <script type="text/javascript">
        var initComplete = false;

        $(document).bind("pageinit", function () {

            if (!initComplete) {

                $.getJSON("data.json", function (data) {
                    $("ul").append($("#liTmpl").template({ products: data })
                        .filter("*")).listview("refresh");

                    $("a.productLink").bind("tap", function () {
                        var targetFlower = $(this).attr("data-flower");
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                var page = $("#productPage");
                                page.find("#header").text(data[i].label);
                                page.find("#image").attr("src", data[i].name + ".png");
                                page.find("#description").text(data[i].text);
                                page.find("#price").text(data[i].price);

                                $.mobile.changePage("#productPage");
                                break;
                            }
                        }
                    })
                });
                initComplete = true;
            }
        })
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true>

            </ul>
        </div>
    </div>
    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div><b>Price: <span id="price"></span></b></div>
             </div>
         </div>
     </div>
    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            Basket will go here
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
        </div>
    </div>
</body>
</html>

image 提示这是一种特别适合 jQuery Mobile 的方法,因为多个页面包含在一个 HTML 文档中。通常,由于移动设备固有的局限性,您希望尽可能保持 HTML 文档的简单性。

我从文档中删除了一个数据模板,并添加了一个新页面(它的idproductPage),我用它来表示每一朵花。我修改了用于生成列表项的模板,以便在href属性中没有目标页面,并添加我自己的数据属性,以便我知道任何给定链接与哪朵花相关。当从 JSON 中检索到数据后,修改后的脚本从我刚刚使用模板创建的列表元素中选择所有针对每个产品的链接,并绑定到tap事件。当点击一个列表项时,我找到合适的数据项并使用其属性来配置productPage页面,设置向用户显示的文本和图像,如下所示:

...
<script type="text/javascript">

    var initComplete = false;

    $(document).bind("pageinit", function () {
        if (!initComplete) {
            $.getJSON("data.json", function (data) {
                $("ul").append($("#liTmpl").template({ products: data })
                    .filter("*")).listview("refresh");

                $("a.productLink").bind("tap", function () {
                    var targetFlower = $(this).attr("data-flower");
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            var page = $("#productPage");
                            page.find("#header").text(data[i].label);
                            page.find("#image").attr("src", data[i].name + ".png");
                            page.find("#description").text(data[i].text);
                            page.find("#price").text(data[i].price);

                            $.mobile.changePage("#productPage");
                            break;
                        }
                    }
                })
            });
            initComplete = true;
        }
    })
</script>
...

在我配置页面之后,我使用changePage方法来触发导航。这个例子的外观没有变化,但是移动浏览器需要管理的元素更少了,这是一个很好的例子,说明了如何操作 jQuery Mobile 文档的页面结构。

创建购物车

我在这个例子中使用了一个分割列表,列表项的左侧指向basket页面。在这一节中,我将定义页面的元素并添加一些 JavaScript,这样就有了一个简单的篮子。清单 33-5 显示了对文档的修改。

清单 33-5 。实施购物篮

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script id="liTmpl" type="text/x-handlebars-template">
        {{#products}}
        <li>
            <a href="#" class="buy" id="{{name}}">{{label}}</a>
            <a class="productLink" data-flower="{{name}}" href="#">{{label}}</a>
        </li>
        {{/products}}
    </script>
    <script id="trTmpl" type="text/x-handlebars-template">
        <tr data-price="{{price}}" id="{{name}}"><td>{{label}}</td><td id="count">1</td>
            <td id="subtotal">0</td></tr>
    </script>
    <script type="text/javascript">
        $(document).ready(function () {
            $.getJSON("data.json", function (data) {
                $("ul").append($("#liTmpl").template({ products: data }))
                    .filter("*").listview("refresh");

                $("a.productLink").bind("tap", function () {
                    var targetFlower = $(this).attr("data-flower");
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            var page = $("#productPage");
                            page.find("#header").text(data[i].label);
                            page.find("#image").attr("src", data[i].name + ".png");
                            page.find("#description").text(data[i].text);
                            page.find("#price").text(data[i].price);
                            $.mobile.changePage("#productPage");
                            break;
                        }
                    }
                });

                $("a.buy").bind("tap", function () {
                    var targetFlower = this.id;
                    var row = $("#basketTable tbody #" + targetFlower);
                    if (row.length > 0) {
                        var countCell = row.find("#count");
                        countCell.text(Number(countCell.text()) + 1);
                    } else {
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                $("#trTmpl").template(data[i])
                                    .appendTo("#basketTable tbody")
                                break;
                            }
                        }
                    }
                    calculateTotals();
                    $.mobile.changePage("#basket")
                });
            })
        })

        function calculateTotals() {
            var total = 0;
            $("#basketTable tbody").children().each(function (index, elem) {
                var count = Number($(elem).find("#count").text())
                var price = Number($(elem).attr("data-price").slice(1))
                var subtotal = count * price;
                $(elem).find("#subtotal").text("$" + subtotal.toFixed(2));
                total += subtotal;
            })
            $("#total").text("$" + total.toFixed(2))
        }
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
        table {display: inline-block; margin: auto; margin-top: 20px; text-align: left;
            border-collapse: collapse}
        td {min-width: 100px}
        th, td {text-align: right}
        th:nth-child(1), td:nth-child(1) {text-align: left}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>
    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div><b>Price: <span id="price"></span></b></div>
             </div>
         </div>
     </div>
    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            <table id="basketTable" border=0>
                <thead>
                    <tr><th>Flower</th><th>Quantity</th><th>Subtotal</th></tr>
                </thead>
                <tbody></tbody>
                <tfoot>
                    <tr><th colspan=2>Total:</th><td id="total"></td></tr>
                </tfoot>
            </table>
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
            <button data-inline="true">Checkout</button>
        </div>
    </div>
</body>
</html>

我向basket页面添加了一个table,它为每个选中的产品显示一行。每行显示产品名称、数量和小计。表格中有一个页脚显示了总的总数。我绑定到了tap事件,这样当用户点击左侧的拆分按钮时,要么向表中添加一个新行,要么如果表中已经有该产品的一行,数量就会增加。新行是使用另一个数据模板生成的,其他一切都是通过读取文档中元素的内容来处理的。

我使用 DOM 本身来确定和维护客户购物篮的整个状态。我本来可以创建一个 JavaScript 对象来对订单进行建模,并从该对象中驱动表的内容,但是在一本关于 jQuery 的书中,我喜欢利用一切机会来处理文档本身。结果是一个简单的篮子,如图图 33-2 所示。

9781430263883_Fig33-02.jpg

图 33-2 。购物篮页面

添加数量变化

篮子是功能性的,但是如果用户想要两朵玫瑰,例如,她必须点击Rose列表项目,点击Back按钮,然后再次点击Rose项目。这个过程非常荒谬,所以为了更容易地改变产品的数量,我在表中添加了一些input元素。您可以在清单 33-6 中看到这些变化。

清单 33-6 。将范围滑块添加到购物篮表格

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script id="liTmpl" type="text/x-handlebars-template">
        {{#products}}
        <li>
            <a href="#" class="buy" id="{{name}}">{{label}}</a>
            <a class="productLink" data-flower="{{name}}" href="#">{{label}}</a>
        </li>
        {{/products}}
    </script>
    <script id="trTmpl" type="text/x-handlebars-template">
        <tr data-theme="b" data-price="{{price}}" id="{{name}}"><td>{{label}}</td>
            <td id="count"><input type=number value=1 min=0 max=10></td>
            <td id="subtotal">0</td>
        </tr>
    </script>
    <script type="text/javascript">

        var initComplete = false;

        $(document).bind("pageinit", function () {
            if (!initComplete) {
                $.getJSON("data.json", function (data) {

                    $("ul").append($("#liTmpl")
                        .template({ products: data })).listview("refresh");

                    $("a.productLink").bind("tap", function () {
                        var targetFlower = $(this).attr("data-flower");
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                var page = $("#productPage");
                                page.find("#header").text(data[i].label);
                                page.find("#image").attr("src", data[i].name + ".png");
                                page.find("#description").text(data[i].text);
                                page.find("#price").text(data[i].price);

                                $.mobile.changePage("#productPage");
                                break;
                            }
                        }
                    })

                    $("a.buy").bind("tap", function () {
                        var targetFlower = this.id;
                        var row = $("#basketTable tbody #" + targetFlower);
                        if (row.length > 0) {
                            var countCell = row.find("#count input");
                            countCell.val(Number(countCell.val()) + 1);
                        } else {
                            for (var i = 0; i < data.length; i++) {
                                if (data[i].name == targetFlower) {
                                    $("#trTmpl").template(data[i])
                                        .appendTo("#basketTable tbody")
                                        .find("input").textinput()

                                    break;
                                }
                            }
                        }
                        calculateTotals();
                        $.mobile.changePage("#basket")
                    })

                    $(document).on("change click", "input", function (event) {
                        calculateTotals();
                    })
                });
                initComplete = true;
            }
        })

        function calculateTotals() {
            var total = 0;
            $("#basketTable tbody").children().each(function (index, elem) {
                var count = Number($(elem).find("#count input").val())
                var price = Number($(elem).attr("data-price").slice(1))
                var subtotal = count * price;
                $(elem).find("#subtotal").text("$" + subtotal.toFixed(2));
                total += subtotal;
            })
            $("#total").text("$" + total.toFixed(2))
        }
    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding-top: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
        table {display: inline-block; margin: auto; margin-top: 20px; text-align: left;
            border-collapse: collapse}
        td {min-width: 100px; padding-bottom: 10px}
        td:nth-child(2) {min-width: 75px; width: 75px}
        th, td {text-align: right}
        th:nth-child(1), td:nth-child(1) {text-align: left}
        input[type=number] {background-color: white}
        tfoot tr {border-top: medium solid black}
        tfoot tr td {padding-top: 10px}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>
    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div><b>Price: <span id="price"></span></b></div>
             </div>
         </div>
     </div>
    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            <table id="basketTable" border=0>
                <thead>
                    <tr><th>Flower</th><th>Quantity</th><th>Subtotal</th></tr>
                </thead>
                <tbody></tbody>
                <tfoot>
                    <tr><th colspan=2>Total:</th><td id="total"></td></tr>
                </tfoot>
            </table>
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
            <button data-inline="true">Checkout</button>
        </div>
    </div>
</body>
</html>

我在模板的 quantity 单元格中插入了一个input元素,用于为表格生成行。这个input元素的typenumber,这导致一些浏览器在文本输入区域旁边插入小的上下按钮。这些按钮太小,不适合触摸,但浏览器也会过滤字符,丢弃任何不适合数字的内容。虽然对于本章来说这是可以接受的,但是对于实际项目来说这不是一个完美的方法,因为它支持浮点数,这意味着用户可以输入产品的分数。

在 jQuery Mobile 增强了页面之后,当我向文档添加input元素时,我调用了textinput方法:

...
$("#trTmpl").template(data[i]).appendTo("#basketTable tbody").find("input").textinput()
...

如果我不添加这个方法调用,浏览器会显示本机的input元素。调用textinput方法会导致 jQuery Mobile 增强元素,尽管它没有正确分配样本。所以我为input元素定义了一个样式来设置一致的背景颜色:

...
input[type=number] {background-color: white}
...

我需要更频繁地计算小计和总计,因为用户可以在购物篮页面中更改产品的数量。因为我在应用的整个生命周期中向文档添加了input元素,所以我使用 jQuery on方法来处理事件。on方法在第九章中有描述。下面是事件处理程序代码:

...
$(document).on("change click", "input", function (event) {
    calculateTotals();
})
...

我使用on方法将我的处理函数与changeclick事件关联起来。为数字input元素添加向上和向下按钮的浏览器在这些按钮被按下时会触发 click 事件,所以除了更令人期待的change事件之外,我还需要处理这个事件。当任一事件被触发时,我的处理函数调用calculateTotals函数。你可以在图 33-3 中看到篮子的样子。

9781430263883_Fig33-03.jpg

图 33-3 。向购物篮页面添加输入元素

向信息页面添加按钮

产品信息描述了用户选择的花,但是它没有为用户提供任何将它添加到购物篮的方法。为了完善基本的购物篮功能,我在产品页面上添加了一个按钮,将商品添加到购物篮中。清单 33-7 显示了产品页面的变化。

清单 33-7 。向产品页面添加按钮

...
<div id="productPage" data-role="page" data-theme="b">
     <div data-role="header">
        <h1 id="header"></h1>
     </div>
     <div>
         <div class="lcontainer">
             <img id="image" src="">
             <div><a href="#" data-rel="back" data-role="button"
                    data-inline=true data-direction="reverse">Back</a>
             </div>
         </div>
         <div class="productData">
             <span id="description"></span>
             <div>
                <b>Price: <span id="price"></span></b>
                <a href="#" id="buybutton" data-flower="" data-role="button"
                   data-inline=true>Buy</a>
             </div>
         </div>
     </div>
 </div>
...

我定义了一个a元素,jQuery Mobile 将把它转换成一个按钮小部件。我添加了一个数据属性(data-flower),这样当用户点击按钮时,我可以跟踪显示的是哪朵花。为了支持这个按钮,我对脚本做了一些修改。这些变化显示在清单 33-8 中。

清单 33-8 。在脚本中添加对购买按钮的支持

...
<script type="text/javascript">

    var initComplete = false;

    $(document).bind("pageinit", function () {
        if (!initComplete) {
            $.getJSON("data.json", function (data) {

                $("ul").append($("#liTmpl")
                    .template({ products: data })).listview("refresh");

                $("a.productLink").bind("tap", function () {
                    var targetFlower = $(this).attr("data-flower");
                    for (var i = 0; i < data.length; i++) {
                        if (data[i].name == targetFlower) {
                            var page = $("#productPage");
                            page.find("#header").text(data[i].label);
                            page.find("#image").attr("src", data[i].name + ".png");
                            page.find("#description").text(data[i].text);
                            page.find("#price").text(data[i].price);
                            page.find("#buybutton").attr("data-flower", data[i].name);
                            $.mobile.changePage("#productPage");
                            break;
                        }
                    }
                })

                $("#buybutton").bind("tap", function () {
                    addProduct($(this).attr("data-flower"));
                })

                $("a.buy").bind("tap", function () {
                    addProduct(this.id);
                })

                function addProduct(targetFlower) {
                    var row = $("#basketTable tbody #" + targetFlower);
                    if (row.length > 0) {
                        var countCell = row.find("#count input");
                        countCell.val(Number(countCell.val()) + 1);
                    } else {
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                $("#trTmpl").template(data[i])
                                    .appendTo("#basketTable tbody")
                                    .find("input").textinput()

                                break;
                            }
                        }
                    }
                    calculateTotals();
                    $.mobile.changePage("#basket")
                }

                $(document).on("change click", "input", function (event) {
                    calculateTotals();
                })
            });
            initComplete = true;
        }
    })

    function calculateTotals() {
        var total = 0;
        $("#basketTable tbody").children().each(function (index, elem) {
            var count = Number($(elem).find("#count input").val())
            var price = Number($(elem).attr("data-price").slice(1))
            var subtotal = count * price;
            $(elem).find("#subtotal").text("$" + subtotal.toFixed(2));
            total += subtotal;
        })
        $("#total").text("$" + total.toFixed(2))
    }
</script>
...

这些变化非常简单。当用户从主列表中选择一个产品时,我在a元素上设置了data-flower属性的值。我注册了一个函数来处理按钮的tap事件,并使用值data-flower来调用addProduct函数,该函数包含我从另一个处理函数中提取的代码。通过这些更改,用户可以从主列表(通过点击拆分列表项目的左侧)或从信息页面(通过点击Buy按钮)向购物篮添加产品。图 33-4 显示了页面上添加的Buy按钮。

9781430263883_Fig33-04.jpg

图 33-4 。向产品信息页面添加按钮

实现结账流程

为了完善这个例子,我将演示如何从各种 jQuery Mobile 页面收集数据,收集的数据可以用于发出 Ajax 请求。我不会发出请求本身,也不会实现服务器。jQuery Mobile 使用了对 Ajax 的核心 jQuery 支持,我在第十四章和第十五章描述了这一点。清单 33-9 显示了当点击Checkout按钮时显示给用户的页面,以及收集数据的处理函数。

清单 33-9 。实施结账流程

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="jquery.mobile-1.3.1.css" type="text/css" />
    <script type="text/javascript" src="jquery-1.10.1.js"></script>
    <script src="handlebars.js"></script>
    <script src="handlebars-jquery.js"></script>
    <script id="liTmpl" type="text/x-handlebars-template">
        {{#products}}
        <li>
            <a href="#" class="buy" id="{{name}}">{{label}}</a>
            <a class="productLink" data-flower="{{name}}" href="#">{{label}}</a>
        </li>
        {{/products}}
    </script>
    <script id="trTmpl" type="text/x-handlebars-template">
        <tr data-theme="b" data-price="{{price}}" id="{{name}}"><td>{{label}}</td>
            <td id="count"><input type=number value=1 min=0 max=10></td>
            <td id="subtotal">0</td>
        </tr>
    </script>
    <script type="text/javascript">
        var initComplete = false;
        $(document).bind("pageinit", function () {
            if (!initComplete) {
                $.getJSON("data.json", function (data) {
                    $("ul").append($("#liTmpl")
                        .template({ products: data })).listview("refresh");

                    $("a.productLink").bind("tap", function () {
                        var targetFlower = $(this).attr("data-flower");
                        for (var i = 0; i < data.length; i++) {
                            if (data[i].name == targetFlower) {
                                var page = $("#productPage");
                                page.find("#header").text(data[i].label);
                                page.find("#image").attr("src", data[i].name + ".png");
                                page.find("#description").text(data[i].text);
                                page.find("#price").text(data[i].price);
                                page.find("#buybutton")
                                    .attr("data-flower", data[i].name);
                                $.mobile.changePage("#productPage");
                                break;
                            }
                        }
                    })

                    $("#buybutton").bind("tap", function () {
                        addProduct($(this).attr("data-flower"));
                    })

                    $("a.buy").bind("tap", function () {
                        addProduct(this.id);
                    })

                    function addProduct(targetFlower) {
                        var row = $("#basketTable tbody #" + targetFlower);
                        if (row.length > 0) {
                            var countCell = row.find("#count input");
                            countCell.val(Number(countCell.val()) + 1);
                        } else {
                            for (var i = 0; i < data.length; i++) {
                                if (data[i].name == targetFlower) {
                                    $("#trTmpl").template(data[i])
                                        .appendTo("#basketTable tbody")
                                        .find("input").textinput();
                                    break;
                                }
                            }
                        }
                        calculateTotals();
                        $.mobile.changePage("#basket")
                    }

                    $(document).on("change click", "input", function (event) {
                        calculateTotals();
                    })

                    $("#submit").bind("tap", function () {
                        var dataObject = new Object();
                        $("#basketTable tbody").children().each(function (index, elem) {
                            dataObject[elem.id] = $(elem).find("#count input").val();
                        })
                        dataObject["name"] = $("#name").val();
                        dataObject["wrap"] = $("option:selected").val();
                        dataObject["shipping"] = $("input:checked").attr("id")

                        console.log("DATA: " + JSON.stringify(dataObject))
                    })
                });
                initComplete = true;
            }
        })

        function calculateTotals() {
            var total = 0;
            $("#basketTable tbody").children().each(function (index, elem) {
                var count = Number($(elem).find("#count input").val())
                var price = Number($(elem).attr("data-price").slice(1))
                var subtotal = count * price;
                $(elem).find("#subtotal").text("$" + subtotal.toFixed(2));
                total += subtotal;
            })
            $("#total").text("$" + total.toFixed(2))
        }

    </script>
    <script type="text/javascript" src="jquery.mobile-1.3.1.js"></script>
    <style type="text/css">
        .lcontainer {float: left; text-align: center; padding: 10px}
        .productData {float: right; padding: 10px; width: 60%}
        .cWrapper {text-align: center}
        table {display: inline-block; margin: auto; margin-top: 20px; text-align: left;
            border-collapse: collapse}
        td {min-width: 100px; padding-bottom: 10px}
        td:nth-child(2) {min-width: 75px; width: 75px}
        th, td {text-align: right}
        th:nth-child(1), td:nth-child(1) {text-align: left}
        input[type=number] {background-color: white}
        tfoot tr {border-top: medium solid black}
        tfoot tr td {padding-top: 10px}
    </style>
</head>
<body>
    <div id="page1" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div id="container" style="padding: 20px">
            <ul data-role="listview" data-inset=true></ul>
        </div>
    </div>
    <div id="productPage" data-role="page" data-theme="b">
         <div data-role="header">
            <h1 id="header"></h1>
         </div>
         <div>
             <div class="lcontainer">
                 <img id="image" src="">
                 <div><a href="#" data-rel="back" data-role="button"
                        data-inline=true data-direction="reverse">Back</a>
                 </div>
             </div>
             <div class="productData">
                 <span id="description"></span>
                 <div>
                    <b>Price: <span id="price"></span></b>
                    <a href="#" id="buybutton" data-flower="" data-role="button"
                       data-inline=true>Buy</a>
                 </div>
             </div>
         </div>
     </div>
    <div id="basket" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div class="cWrapper">
            <table id="basketTable" border=0>
                <thead>
                    <tr><th>Flower</th><th>Quantity</th><th>Subtotal</th></tr>
                </thead>
                <tbody></tbody>
                <tfoot>
                    <tr><th colspan=2>Total:</th><td id="total"></td></tr>
                </tfoot>
            </table>
        </div>
        <div class="cWrapper">
            <a href="#" data-rel="back" data-role="button" data-inline=true
               data-direction="reverse">Back</a>
            <a href="#checkout" data-role="button" data-inline="true">Checkout</a>
        </div>
    </div>
   <div id="checkout" data-role="page" data-theme="b">
        <div data-role="header">
           <h1>Jacqui's Shop</h1>
        </div>
        <div data-role="content">
            <label for="name">Name: </label>
            <input id="name" placeholder="Your Name">

            <label for="wrap"><span>Gift Wrap: </span></label>
            <select id="wrap" name="wrap" data-role="slider">
                <option value="yes" selected>Yes</option>
                <option value="no">No</option>
            </select>

            <fieldset data-role="controlgroup" data-type="horizontal">
                <legend>Shipping:</legend>
                <input type="radio" name="ship" id="overnight" checked />
                <label for="overnight">Overnight</label>
                <input type="radio" name="ship" id="23day"/>
                <label for="23day">2-3 days</label>
                <input type="radio" name="ship" id="710day"/>
                <label for="710day">7-10 days</label>
            </fieldset>

            <div class="cWrapper">
                <a href="#" data-rel="back" data-role="button" data-inline="true"
                   data-direction="reverse">Back</a>
                <a href="#" id="submit" data-role="button"
                   data-inline="true">Submit Order</a>
            </div>
        </div>
    </div>
</body>
</html>

这个新页面叫做checkout。我保持这个表单简单,提示用户输入姓名,并提供礼物包装和运送方式的选择。你可以在图 33-5 中看到该页面是如何出现的。我以纵向显示页面,因为它允许我显示所有的元素,而不必滚动。

9781430263883_Fig33-05.jpg

图 33-5 。结账页面

当用户点击Submit Order按钮时,我从 HTML 文档的不同页面收集数据,并将结果作为 JSON 字符串写入控制台。以下是这种字符串的一个示例:

{"carnation":"3","rose":"1","orchid":"1",
    "name":"Adam Freeman","wrap":"yes","shipping":"23day"}

摘要

在这一章中,我采用了 jQuery Mobile 的一些核心特性,并将它们组合起来,创建了一个花店示例的简单移动实现。就其本质而言,jQuery Mobile 比 jQuery UI 简单得多。主要的挑战是设计一种方法,在一个小屏幕的范围内给用户提供他所需要的信息。

三十四、使用 jQuery 工具方法

jQuery 包括许多实用方法,它们对 jQuery 对象执行高级操作,或者补充 JavaScript 语言以提供编程语言中通常存在的特性。您可能永远都不需要这些方法,但是 jQuery 在内部使用它们,公开使用它们意味着当您遇到 jQuery 团队已经解决的奇怪问题时,您可以节省时间和精力。

其中一些方法应用于 jQuery 对象,一些方法针对主 jQuery 函数调用,我已经用$符号进行了说明(在第五章中进行了描述)。表 34-1 对本章进行了总结。

表 34-1 。章节总结

问题 解决办法 列表
将操作排队以供以后执行。 使用通用队列。 1, 2
过滤数组的内容。 使用grep方法。 3, 4
确定数组是否包含特定的对象或值。 使用inArray方法。 five
投影数组的内容。 使用map方法。 6, 7
连接两个数组。 使用merge方法。 eight
从一个jQuery对象中删除重复项,并按照它们在文档中出现的顺序对它们进行排序。 使用unique方法。 nine
确定对象的类型。 使用isXXXtype方法。 10, 11
准备提交表单的内容。 使用serializeserializeArray方法。 Twelve
将数据解析成更有用的形式。 使用parseJSONparseXML方法。 Thirteen
从字符串中删除前导和尾随空格。 使用trim方法。 Fourteen
确定一个元素是否包含另一个元素。 使用contains方法。 Fifteen

重新访问队列:使用通用队列

在第十章中,我向您展示了如何使用 jQuery 效果队列来管理应用于一组元素的一连串效果。事实上,效果队列只是一个队列,这个特性是一个通用的队列,可以有更广泛的用途。表 34-2 重述了队列相关的方法,为通用目的进行了调整。

表 34-2 。队列方法

方法 描述
clearQueue(<name>) 移除指定队列中尚未运行的任何函数。
queue(<name>) 返回要对jQuery对象中的元素执行的函数的指定队列。
queue(<name>, function) 将函数添加到队列的末尾。
dequeue(<name>) jQuery对象中的元素删除并执行队列中的第一项。
delay(<time>, <name>) 在指定队列中的效果之间插入延迟。

当在没有指定队列名的情况下使用这些方法时,jQuery 默认为fx,这是用于视觉效果的队列。我可以使用任何其他队列名来创建函数队列。

当将 jQuery 队列应用于一般用途时,我使用clearQueue方法而不是stop方法–stop对不适于更广泛使用的 jQuery 效果有特殊支持。清单 34-1 提供了一个使用通用队列的例子。

清单 34-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"/>
    <script type="text/javascript">
        $(document).ready(function() {

            var elems = $("input");

            elems.queue("gen", function(next) {
                $(this).val(100).css("border", "thin red solid");
                next();
            });

            elems.delay(1000, "gen");

            elems.queue("gen", function(next) {
                $(this).val(0).css("border", "");
                $(this).dequeue("gen");
            });

           $("<button>Process Queue</button>").appendTo("#buttonDiv")
                .click(function(e) {
                    elems.dequeue("gen");
                    e.preventDefault();
                });

        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post">
        <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" required />
                    </div>
                    <div class="dcell">
                        <img src="daffodil.png"/><label for="daffodil">Daffodil:</label>
                        <input name="daffodil" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="rose.png"/><label for="rose">Rose:</label>
                        <input name="rose" value="0" required />
                    </div>
                </div>
                <div id="row2"class="drow">
                    <div class="dcell">
                        <img src="peony.png"/><label for="peony">Peony:</label>
                        <input name="peony" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="primula.png"/><label for="primula">Primula:</label>
                        <input name="primula" value="0" required />
                    </div>
                    <div class="dcell">
                        <img src="snowdrop.png"/><label for="snowdrop">Snowdrop:</label>
                        <input name="snowdrop" value="0" required />
                    </div>
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

image 提示我在这个例子中引用的styles.css文件是我在本书第二部分中使用的文件。

在这个例子中,我将三个函数添加到一个名为gen 的队列中,该队列对文档中的input元素进行操作。首先,我使用val方法将所有的input值设置为100,使用css方法添加一个边框。第二,我使用 delay 方法向队列添加一秒钟的延迟。最后,我使用valcss方法将input元素重置为它们的初始状态。

我还在文档中添加了一个button,它调用了dequeue方法——与效果队列不同,我自己负责启动队列处理。你可以在图 34-1 中看到效果。

9781430263883_Fig34-01.jpg

图 34-1 。使用通用队列

我放在队列中的函数的工作方式与事件队列中的相同,和以前一样,我负责调用dequeue方法或调用作为参数传递的函数。我倾向于使用函数参数——只是因为我在调用dequeue方法时经常忘记指定队列名,这意味着我的队列陷入停顿。

手动处理队列项目

当然,您不必从另一个函数中触发一个队列函数——您可以依靠外部触发器来dequeue每个项目,比如用户按下我添加到文档中的按钮。清单 34-2 展示了如何做到这一点。

清单 34-2 。显式出队函数

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

        $("input").queue("gen", function() {
            $(this).val(100).css("border", "thin red solid");
        }).queue("gen", function() {
            $(this).val(0).css("border", "");
        }).queue("gen", function() {
            $(this).css("border", "thin blue solid");
            $("#dequeue").attr("disabled", "disabled");
        });

       $("<button id=dequeue>Dequeue Item</button>").appendTo("#buttonDiv")
            .click(function(e) {
                $("input").dequeue("gen");
                e.preventDefault();
            });
    });
</script>
...

在这个脚本中,我将queue调用链接在一起,并添加了一个函数,该函数设置所选元素的边框并禁用button元素,必须单击该元素才能处理队列中的每个项目——没有自动链接。

数组的实用方法

jQuery 提供了许多使用数组的有用方法——这些方法在表 34-3 中有描述。在大多数情况下,使用HTMLElement数组有更好的方法——只需使用标准的 jQuery 方法来处理和过滤元素。对于其他类型的数组,这些方法可能会有所帮助。

表 34-3 。使用数组的实用方法

方法 描述
$.grep(<array>, function) $.grep(<array>, function, <invert>) 基于函数过滤数组的内容。
$.inArray(<value>, <array>) 确定数组中是否包含特定的项。
$.map(<array>, function) $.map(<array>, <map>) 使用函数投影数组或映射对象。
$.merge(<array>, <array>) 将第二个数组的内容追加到第一个数组中。
$.unique(HTMLElement[]) 将一组HTMLElement对象按文档顺序排序,并删除任何重复的对象。

使用 Grep 方法

grep方法允许我们找到一个数组中所有与过滤函数匹配的元素。清单 34-3 展示了这种方法。

清单 34-3 。使用 Grep 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        var flowerArray = ["aster", "daffodil", "rose", "peony", "primula", "snowdrop"];

        var filteredArray = $.grep(flowerArray, function(elem, index) {
            return elem.indexOf("p") > -1;
        });

        for (var i = 0; i < filteredArray.length; i++) {
            console.log("Filtered element: " + filteredArray[i]);
        }  
    });
</script>
...

我们的过滤函数传递了两个参数——第一个是数组中的元素,第二个是该元素的数组索引。对于数组中的每一项都调用我们的函数,如果当前项包含在筛选结果中,则返回true

在这个例子中,我对字符串数组使用了grep方法,过滤掉了那些不包含字母p的字符串。我将过滤后的数组的内容写入控制台,产生以下结果:

Filtered element: peony
Filtered element: primula
Filtered element: snowdrop

您可以向grep方法提供一个额外的参数——如果这个参数是true,那么过滤过程将被反转,结果将包含该函数过滤掉的那些元素。清单 34-4 显示了这一论点的效果。

清单 34-4 。使用 Grep 方法反转选择

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

        var flowerArray = ["aster", "daffodil", "rose", "peony", "primula", "snowdrop"];

        var filteredArray = $.grep(flowerArray, function(elem, index) {
            return elem.indexOf("p") > -1;
        },true);

        for (var i = 0; i < filteredArray.length; i++) {
            console.log("Filtered element: " + filteredArray[i]);
        }  

    });
</script>
...

这种变化会产生以下结果:

Filtered element: aster
Filtered element: daffodil
Filtered element: rose

使用阵列法

inArray方法确定一个数组是否包含一个指定的值——如果该项在数组中,该方法返回该项的索引,否则返回-1。清单 34-5 展示了inArray方法。

清单 34-5 。使用 inArray 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        var flowerArray = ["aster", "daffodil", "rose", "peony", "primula", "snowdrop"];
        console.log("Array contains rose: " +$.inArray("rose", flowerArray));
        console.log("Array contains lily: " +$.inArray("lily", flowerArray));
    });
</script>
...

这个脚本检查花的数组是否包含roselily。结果如下:

Array contains rose: 2
Array contains lily: -1

使用贴图法

map方法使用一个函数将数组或地图对象的内容投影到一个新的数组中,使用一个函数来确定每个项目在结果中的表示方式。清单 34-6 展示了map方法在一个数组中的使用。

清单 34-6 。使用映射方法投影数组

...
<script type="text/javascript">
    $(document).ready(function() {
        var flowerArray = ["aster", "daffodil", "rose", "peony", "primula", "snowdrop"];

        var result = $.map(flowerArray, function(elem, index) {
            return index + ": " + elem;
        });

        for (var i = 0; i < result.length; i++) {
            console.log(result[i]);
        }  
     });
</script>
...

我们的映射函数针对数组中的每一项执行,并将该项及其在数组中的索引作为参数传递给它。函数的结果包含在由map方法返回的数组中。在这个脚本中,我通过连接值和索引来转换数组中的每一项,产生以下结果:

0: aster
1: daffodil
2: rose
3: peony
4: primula
5: snowdrop

您可以使用 map 方法有选择地投影一个数组——如果您没有从函数中为正在处理的项返回值,结果中将没有相应的项。清单 34-7 显示了如何有选择地从一个数组中投影。

清单 34-7 。选择性映射数组

...
<script type="text/javascript">
    $(document).ready(function() {
        var flowerArray = ["aster", "daffodil", "rose", "peony", "primula", "snowdrop"];

        var result = $.map(flowerArray, function(elem, index) {
            if (elem != "rose") {
                return index + ": " + elem;
            }  
        });

        for (var i = 0; i < result.length; i++) {
            console.log(result[i]);
        }
    });
</script>
...

为除rose之外的所有数组值生成结果,这产生以下结果:

0: aster
1: daffodil
3: peony
4: primula
5: snowdrop

使用合并方法

merge方法连接两个数组,如清单 34-8 所示。

清单 34-8 。使用合并方法

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

        var flowerArray = ["aster", "daffodil", "rose", "peony", "primula", "snowdrop"];
        var additionalFlowers = ["carnation", "lily", "orchid"];

        $.merge(flowerArray, additionalFlowers);

        for (var i = 0; i < flowerArray.length; i++) {
            console.log(flowerArray[i]);
        }  
     });
</script>
...

第二个数组中的项被追加到第一个数组中,由第一个参数指定的数组被合并过程修改。示例中的脚本产生以下结果:

aster
daffodil
rose
peony
primula
snowdrop
carnation
lily
orchid

使用独特的方法

unique方法将一组HTMLElement对象按照它们在文档中出现的顺序进行排序,并删除任何重复的元素。清单 34-9 展示了如何使用这个方法。

清单 34-9 。使用独特的方法

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

        var selection = $("img[src*=rose], img[src*=primula]").get();
        $.merge(selection, $("img[src*=aster]"));
        $.merge(selection, $("img"));

        $.unique(selection);

        for (var i =0; i < selection.length; i++) {
            console.log("Elem: " + selection[i].src);
        }  

    });
</script>
...

排序过程就地完成,这意味着作为参数传递给unique方法的数组被修改。在这个例子中,我创建了一个包含重复且不按文档顺序排列的HTMLElement对象的数组,然后应用了unique方法。

类型的实用方法

jQuery 提供了一组用于确定 JavaScript 对象性质的方法——这些方法在表 34-4 中有描述。

表 34-4 。使用类型的实用方法

方法 描述
$.isArray(Object) 如果对象是数组,则返回true
$.isEmptyObject(Object) 如果对象没有定义任何方法或属性,则返回true
$.isFunction(Object) 如果对象是函数,则返回true
$.isNumeric(Object) 如果对象是数字,则返回true
$.isWindow(Object) 如果对象是一个Window,则返回true
$.isXMLDoc(Object) 如果对象是 XML 文档,则返回true
$.type(Object) 返回对象的内置 JavaScript 类型。

这些方法大多数都很简单——将一个对象传递给该方法,如果该对象是该方法检测到的类型,则该方法返回true,否则返回false。作为一个简单的演示,清单 34-10 包含了一个使用isFunction方法 的例子。

清单 34-10 。使用 isFunction 方法

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

        function myFunc() {
            console.log("Hello!");
        }  

        console.log("IsFunction: " +$.isFunction(myFunc));
        console.log("IsFunction: " +$.isFunction("hello"));
    });
</script>
...

在这个例子中,我使用了isFunction方法来测试两个对象。结果如下:

IsFunction: true
IsFunction: false

使用类型方法

type方法略有不同,因为它返回对象的基本 JavaScript 类型。结果将是以下字符串之一:

  • boolean
  • number
  • string
  • function
  • array
  • date
  • regexp
  • object

清单 34-11 展示了type方法的使用。

清单 34-11 。使用类型方法

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

        function myFunc() {
            console.log("Hello!");
        }  

        var jq = $("img");
        var elem = document.getElementById("row1");

        console.log("Type: " +$.type(myFunc));
        console.log("Type: " +$.type(jq));
        console.log("Type: " +$.type(elem));
    });
</script>
...

在这个脚本中,我对一个函数、jQuery对象和HTMLElement对象使用了 type 方法。结果如下:

Type: function
Type: object
Type: object

数据的实用方法

jQuery 定义了许多对处理各种数据有用的实用方法——这些方法在表 34-5 中有描述。

表 34-5 。处理数据的实用方法

方法 描述
serialize() 将一组表单元素编码成适合提交给服务器的字符串。
serializeArray() 将一组表单元素编码到一个数组中,准备编码到 JSON 中。
$.parseJSON(<json>) 从 JSON 数据创建一个 JavaScript 对象。
$.parseXML(<xml>) 从 XML 字符串创建一个XMLDocument对象。
$.trim(String) 移除字符串开头和结尾的所有空白。

序列化表单数据

serializeserializeArray方法是一种从一组表单元素中提取细节的便捷方式,对于常规或 Ajax 表单提交非常有用。清单 34-12 展示了这两种方法的使用。

清单 34-12 。序列化表单数据

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

       $("<button>Serialize</button>").appendTo("#buttonDiv").click(function(e) {

            var formArray = $("form").serializeArray();
            console.log("JSON: " + JSON.stringify(formArray))

            var formString = $("form").serialize();
            console.log("String: " + formString)

            e.preventDefault();
        });

    });
</script>
...

在本例中,我使用这两种方法序列化文档中的表单元素,并将结果写入控制台。serializeArray方法返回一个 JavaScript 数组,其中包含文档中每个表单元素的一个对象。这些对象有两个属性:name属性包含元素的name属性的值,而value属性包含元素的值。以下是示例文档的输出:

[{"name":"aster","value":"1"},{"name":"daffodil","value":"0"}, {"name":"rose","value":"0"},{"name":"peony","value":"0"}, {"name":"primula","value":"2"},{"name":"snowdrop","value":"0"}]

相比之下,serialize 方法创建一个编码字符串,如下所示:

aster=1&daffodil=0&rose=0&peony=0&primula=2&snowdrop=0

解析数据

在处理 Ajax 请求的结果时,parseJSONparseXML方法特别有用。对于大多数 web 应用来说,JSON 已经成为首选的数据格式,原因我在第十四章中概述。XML 仍然在使用,但是我发现自己只在将新的应用与遗留的后端系统集成时使用这种 XML 数据。清单 34-13 显示了正在使用的parseJSON方法。

清单 34-13 。解析 JSON 数据

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

       $("<button>Serialize</button>").appendTo("#buttonDiv").click(function(e) {

            var jsonData = '{"name": "Adam Freeman", "city": "London", "country": "UK"}'

            var dataObject = $.parseJSON(jsonData)

            for (var prop in dataObject) {
                console.log("Property: " + prop + " Value: " + dataObject[prop])
            }  

            e.preventDefault();
        });

    });
</script>
...

在这个例子中,我定义了一个简单的 JSON 字符串,并使用parseJSON方法将其转换成一个 JavaScript 对象。然后,我向控制台枚举对象中的属性及其值,生成以下输出:

Property: name Value: Adam Freeman
Property: city Value: London
Property: country Value: UK

修剪琴弦

trim方法 删除字符串开头和结尾的所有空白——包括空格、制表符和换行符。这是大多数编程语言支持的特性,作为其字符数据核心处理的一部分,但是由于某种原因,JavaScript 缺少这一特性。清单 34-14 显示了正在使用的trim方法。

清单 34-14 。使用修剪方法

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

       $("<button>Serialize</button>").appendTo("#buttonDiv").click(function(e) {

            var sourceString = "\n  This string contains whitespace    ";
            console.log(">" + sourceString + "<")

            var resultString = $.trim(sourceString);
            console.log(">" + resultString + "<")

            e.preventDefault();
        });

    });
</script>
...

在这个例子中,我使用了trim方法,将原始的和修整过的字符串写入控制台,产生了以下结果:

>  This string contains whitespace    <
>This string contains whitespace<

其他实用方法

有许多 jQuery 方法并不完全属于另一个类别,但仍然是有用的——这些方法在表 34-6 中有描述。

表 34-6 。其他实用方法

方法 描述
$.contains(HTMLElement, HTMLElement) 如果第一个元素包含第二个元素,则返回true
$.now() 返回当前时间,简写为new Date().getTime()

检查元素包含

方法检查一个元素是否包含另一个元素。两个参数都表示为HTMLElement对象,如果第一个参数表示的元素包含第二个参数表示的元素,则该方法返回 true。清单 34-15 展示了contains方法。

清单 34-15 。使用 Contains 方法

...
<script type="text/javascript">
    $(document).ready(function() {
        $("img").hover(function(e) {
            var elem = document.getElementById("row1");
            if ($.contains(elem, this)) {
                $(e.target).css("border", e.type == "mouseenter" ?
                                            "thick solid red" : "");
            }  
        });
    });
</script>
...

在这个脚本中,我使用 DOM API 获得一个HTMLElement对象,并检查它是否包含传递给事件处理程序方法的元素——如果包含,我为触发事件的元素设置一个边框。

image 提示这个方法只对HTMLElement对象有效——如果你想对jQuery对象执行同样的检查,那么考虑使用find方法,我在第六章中描述过。

摘要

在这一章中,我描述了 jQuery 工具方法——一组有用的函数,可用于对 jQuery 对象执行高级操作,或者补充 JavaScript 语言特性以提供程序员通常需要的支持。当您需要这些方法时,您很高兴它们存在,但是对于大多数 web 应用项目来说,您可以放心地忘记它们。

三十五、jQuery UI 效果和 CSS 框架

在这一章中,我描述了 jQuery UI 提供的两个实用特性。第一个是对现有 jQuery 方法的一组增强,可以动态显示颜色、元素可见性的变化以及 CSS 类的应用。另一个特性是一组 CSS 类,它们将 jQuery UI 主题应用到我们的 HTML 文档的其余部分,以便在整个 web 应用中创建一致的外观。表 35-1 对本章进行了总结。

表 35-1 。章节总结

问题 解决办法 列表
动画颜色变化。 使用增强的animate方法。 one
动画类的应用。 使用增强的addClassremoveClasstoggleClass方法以及switchClass方法。 2, 3
动画显示可见性转换。 使用增强的showhidetoggle方法。 four
在不改变元素可见性的情况下应用效果。 使用effect方法。 five
将元素样式化为小部件。 使用小部件容器类。 six
对元素应用圆角。 使用角类。 seven
将可点击小部件的样式应用于元素。 使用交互状态类。 eight
向用户提供关于元素状态的提示。 使用提示类。 9, 10

使用 jQuery UI 效果

jQuery UI 扩展了一些核心的 jQuery 方法来为一个元素制作不同过渡的动画——从颜色变化的动画到 CSS 类的应用。如果小心使用的话,这些对于 web 应用来说是很有价值的补充,为了补充这些特性,jQuery UI 还定义了一些额外的动画效果。

动画颜色

jQuery UI 扩展了 jQuery animate方法,我在第十章中描述过,增加了对动画颜色的支持。您可以将定义元素颜色的多个 CSS 属性之一制作成动画。表 35-2 描述了animate方法支持的 CSS 属性。

表 35-2 。jQuery UI Animate 方法支持的 CSS 属性

财产 描述
backgroundColor 设置元素的背景色。
borderTopColor``borderBottomColor``borderLeftColor 设置元素边框各边的颜色。
color 设置元素的文本颜色。
outlineColor 设置轮廓的颜色,用于强调元素。

若要制作颜色动画,请将 map 对象作为参数传递给 animate 方法,详细说明要制作动画的属性和目标值。清单 35-1 包含了一个例子。

清单 35-1 。动画颜色

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        #animTarget {
            background-color: white;
            color: black;
            border: medium solid black;
            width: 200px; height: 50px;
            text-align: center;
            font-size: 25px;
            line-height: 50px;
            display: block;
            margin-bottom: 10px;
        }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            $("button").click(function() {
                $("#animTarget").animate({
                    backgroundColor: "black",
                    color: "white"
                })
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div id=animTarget>
        Hello!
    </div>

    <button>Animate Color</button>
</body>
</html>

我设计了这个文档中的div元素的样式,使它的初始背景颜色为white,颜色为black。当单击文档中的按钮时,我调用 animate 方法,指定将这些属性分别更改为blackwhite。从一种颜色到另一种颜色的过渡是逐渐完成的,两种属性同时激活。在图 35-1 中可以看到效果。

9781430263883_Fig35-01.jpg

图 35-1 。动画颜色

image 提示注意,我在style元素中使用了标准的 CSS 属性名,例如–background-color。但是当在 map 对象中指定相同的属性时,我改用了 camel case—backgroundColor。这允许我将 CSS 属性指定为 JavaScript 对象属性,而不必用引号将该术语括起来。

在这个例子中,我使用 CSS 颜色简写值blackwhite指定了我想要的颜色。有许多颜色的简写值,但是animate方法也将接受十六进制颜色(例如#FFFFFF)和 RGB 函数颜色,例如rgb(255, 255, 255)

image 提示除了对颜色属性的支持,你还可以使用animate方法,就像我在第十章中描述的那样。

动画类

jQuery UI 提供了一种使用类来制作 CSS 属性集动画的便捷方式。您只需在一个类中定义属性和值,并告诉 jQuery UI 将该类添加到一个或多个元素中,而不是指定每个属性。jQuery UI 将动画显示从一种状态到另一种状态的转换。清单 35-2 提供了一个演示。

清单 35-2 。使用类制作动画

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .elemClass {
            background-color: white;
            color: black;
            border: medium solid black;
            width: 200px; height: 50px;
            text-align: center;
            font-size: 25px;
            line-height: 50px;
            display: block;
            margin-bottom: 10px;
        }
        .myClass {
            font-size: 40px; background-color: black; color: white;
        }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("button").click(function() {
                if (this.id == "add") {
                    $("#animTarget").addClass("myClass", "fast")
                } else {
                    $("#animTarget").removeClass("myClass", "fast")
                }
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div id=animTargetclass="elemClass">
        Hello!
    </div>

    <button id="add">Add Class</button>
    <button id="remove">Remove Class</button>
</body>
</html>

同样,jQuery UI 扩展了现有的 jQuery 方法来添加功能。在这种情况下,增强的是addClassremoveClass方法。我在第八章中描述了这些方法的标准版本。jQuery UI 版本做了完全相同的事情:方法的第二个参数是 duration,jQuery UI 动画显示了从一个类到另一个类的转换。

在这个例子中,我定义了一个名为myClass的类,文档中有按钮使用fast的持续时间简写来添加和删除这个类。你可以在图 35-2 中看到效果。

9781430263883_Fig35-02.jpg

图 35-2 。使用类制作元素动画

image 提示应用标准的 CSS 样式级联规则,这意味着只有当一个类对于一个或多个目标元素是最特定的时,该类中的属性才会被应用。在前一个例子中,我通过id设置了元素的初始状态,但是在这个例子中,我使用了一个类,这样我的修改就生效了。CSS 样式层叠的细节见第三章。

jQuery UI 还增强了toggleClass方法——这与我在第八章中描述的标准toggleClass方法的工作方式相同,但是采用了一个持续时间参数并使过渡动画化,就像上面的addClassremoveClass示例一样。

切换类别

除了增强一些标准方法之外,jQuery UI 还定义了switchClass方法,该方法删除一个类并添加另一个类,以动画形式显示从一种状态到另一种状态的转换。清单 35-3 包含了一个演示。

清单 35-3 。使用 switchClass 方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .elemClass {
            border: medium solid black;
            width: 200px; height: 50px;
            text-align: center;
            line-height: 50px;
            display: block;
            margin-bottom: 10px;
        }
        .classOne {
            font-size: 25px; background-color: white; color: black;
        }
        .classTwo {
            font-size: 40px; background-color: black; color: white;
        }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("button").click(function() {
                $("#animTarget").switchClass("classOne", "classTwo", "fast")
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div id=animTarget class="elemClass classOne">
        Hello!
    </div>
    <button>Switch Class</button>
</body>
</html>

switchClass方法的参数是应该删除的类、应该添加的类和动画的持续时间。在这个例子中,我的两个类定义了相同的属性,但是这并不是必须的。

使用 jQuery UI 动画

jQuery UI 包括许多可以应用于元素的动画效果,就像第十章中的核心 jQuery 效果一样。我的建议是谨慎使用这些效果。精心制作的动画可以真正提升用户体验——但通常情况下,它们会成为用户烦恼和沮丧的来源。有许多不同的动画效果,包括blindbounceclipdropexplodefadefoldhighlightpuffpulsatescaleshakesizeslide

image 注意在这一章中,我将向你展示如何应用这些效果,但我不打算深入每个单独效果的细节。在http://docs.jquery.com/UI/Effects有一个很好的效果总结和可以应用到其中一些效果的设置。

使用效果来显示和隐藏元素

jQuery UI 增强了 jQuery UI 的showhide,toggle方法来应用动画效果。我在第十章中描述了这些方法的最初版本。要使用这些方法的增强 jQuery UI 版本,请提供附加参数,指定您想要使用的效果以及应用该效果的持续时间。清单 35-4 显示了这些增强方法的使用。

清单 35-4 。使用增强的显示、隐藏和切换方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .elemClass {
            font-size: 25px; background-color: white; color: black;
            border: medium solid black; width: 200px; height: 50px;
            text-align: center; line-height: 50px; display: block; margin-bottom: 10px;
        }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("button").click(function() {
                switch (this.id) {
                    case "show":
                        $("#animTarget").show("fold", "fast");
                        break;
                    case "hide":
                        $("#animTarget").hide("fold", "fast");
                        break;
                    case "toggle":
                        $("#animTarget").toggle("fold", "fast");
                        break;
                }
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <button id="hide">Hide</button>
    <button id="show">Show</button>
    <button id="toggle">Toggle</button>

    <div id=animTarget class="elemClass">
        Hello!
    </div>
</body>
</html>

在这个例子中有三个按钮,点击它们会导致调用showhide,toggle方法。对于所有三个按钮,我已经指定了应该应用fold动画,使用fast持续时间。除了转换是动态的之外,这些方法的工作方式与核心 jQuery 类似。

应用独立效果

jQuery UI 定义了effect方法,它允许我们将动画应用到元素上,而不必显示或隐藏它。当使用正确的动画时,这是吸引用户注意文档中某个元素的有效方法。清单 35-5 包含了一个例子。

清单 35-5 。使用效果方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        .elemClass {
            font-size: 25px; background-color: white; color: black;
            border: medium solid black; width: 200px; height: 50px;
            text-align: center; line-height: 50px; display: block; margin-bottom: 10px;
        }
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            $("button").click(function() {
                $("#animTarget").effect("pulsate", "fast")
            })
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div id=animTarget class="elemClass">
        Hello!
    </div>
    <button>Effect</button>
</body>
</html>

当这个例子中的按钮被点击时,效果被就地应用,而不会对可见性有任何永久的改变。在这种情况下,我使用了pulsate效应,它导致元素脉冲打开和关闭。

使用 jQuery UI CSS 框架

jQuery UI 通过将一组类应用于应用一些复杂 CSS 样式的元素来管理小部件的外观。其中一些类向程序员公开,这样不属于小部件的元素就可以以一致的方式进行样式化——我在本书第四部分的例子中使用了其中一些类。

使用小部件容器类

CSS 框架中三个最基本的类应用了小部件上使用的核心样式。这些类别在表 35-3 中描述。

表 35-3 。jQuery UI 小部件容器类

班级 描述
ui-widget 应用于所有容器元素。
ui-widget-header 应用于标题容器元素。
ui-widget-content 应用于内容容器元素。

这些类被应用于容器元素——也就是那些包含所有头和内容元素的元素(或者,在ui-widget的情况下,最外层的元素)。清单 35-6 展示了如何应用这些类。

清单 35-6 。使用 jQuery UI 小部件容器类

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        body > div {float: left; margin: 10px}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div>
        <div>
            Flowers
        </div>
        <div>
            <div class="dcell">
                <img src="peony.png"/><label for="peony">Peony:</label>
                <input name="peony" value="0" />
            </div>
        </div>
    </div>

    <div class="ui-widget">
        <div class="ui-widget-header">
            Flowers
        </div>
        <div class="ui-widget-content">
            <div class="dcell">
                <img src="peony.png"/><label for="peony">Peony:</label>
                <input name="peony" value="0" />
            </div>
        </div>
    </div>
</body>
</html>

在这个例子中有两组元素,其中一组我已经应用了容器类。你可以在图 35-3 中看到效果。

9781430263883_Fig35-03.jpg

图 35-3 。应用 jQuery UI 小部件容器类

应用圆角

下一组 CSS 框架类让我们将圆角应用于类似小部件的元素。表 35-4 描述了这一类别中的等级。

表 35-4 。jQuery UI 小部件圆角类

班级 描述
ui-corner-all 将元素的所有角变圆。
ui-corner-bl 圆角左下角。
ui-corner-bottom 使左下角和右下角变圆。
ui-corner-br 倒圆角。
ui-corner-left 使左上角和左下角变圆。
ui-corner-right 倒圆角右上角和右下角。
ui-corner-tl 圆角左上角。
ui-corner-top 将左上角和右上角倒圆角。
ui-corner-tr 圆角右上角。

这些类只有在元素有背景或边距时才有效,这意味着它们可以应用于ui-widget-headerui-widget-content类,如清单 35-7 所示。

清单 35-7 。使用圆角类别

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        body > div {float: left; margin: 10px}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div>
        <div>
            Flowers
        </div>
        <div>
            <div class="dcell">
                <img src="peony.png"/><label for="peony">Peony:</label>
                <input name="peony" value="0" />
            </div>
        </div>
    </div>

    <div class="ui-widget">
        <div class="ui-widget-headerui-corner-top"style="padding-left: 5px">
            Flowers
        </div>
        <div class="ui-widget-contentui-corner-bottom">
            <div class="dcell">
                <img src="peony.png"/><label for="peony">Peony:</label>
                <input name="peony" value="0" />
            </div>
        </div>
    </div>
</body>
</html>

为了创造一个整体效果,我把 header 元素的顶角和 content 元素的底角弄圆了。你可以在图 35-4 中看到结果。请注意,我在 header 元素中添加了一些填充——圆角应用于元素的内容框中,这可能需要一些额外的空间来避免剪切内容。

9781430263883_Fig35-04.jpg

图 35-4 。对元素应用圆角

使用交互状态类

您还可以应用 CSS 框架类来显示不同的交互状态,这允许创建以与 jQuery UI 小部件相同的方式响应用户交互的元素。表 35-5 描述了可用的等级。

表 35-5 。jQuery UI 交互类

班级 描述
ui-state-default 应用可点击小工具的默认样式。
ui-state-hover 应用鼠标悬停在可点击小工具上时使用的样式。
ui-state-focus 应用可点击小工具获得焦点时使用的样式。
ui-state-active 应用可点击小工具活动时使用的样式。

清单 35-8 应用了这四个类。请注意,在每种情况下,我都将填充应用于内部的span元素。交互状态类定义填充值,在容器元素和内容之间创建间距的最简单方法是将内部元素作为目标。

清单 35-8 。应用交互状态类

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        body > div {float: left; margin: 10px}
        span {padding: 10px; display: block}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div class="ui-widgetui-state-defaultui-corner-all">
        <span>Default</span>
    </div>
    <div class="ui-widgetui-state-hoverui-corner-all">
        <span>Hover</span>
    </div>
    <div class="ui-widgetui-state-focusui-corner-all">
        <span>Focus</span>
    </div>
    <div class="ui-widgetui-state-activeui-corner-all">
        <span>Active</span>
    </div>
</body>
</html>

你可以在图 35-5 中看到每个职业的效果。其中一些状态在我正在使用的 jQuery UI 主题中是相似的,但是如果需要的话,你可以使用 ThemeRoller(在第十七章中描述)来创建一个增加了状态强调的主题。

9781430263883_Fig35-05.jpg

图 35-5 。交互状态类的效果

使用提示类

一些 CSS 框架类允许我们向用户提供关于文档中元素状态的提示。这些类别在表 35-6 中描述。

表 35-6 。jQuery UI 交互提示类

班级 描述
ui-state-highlight 突出显示一个元素以吸引用户的注意。
ui-state-error 强调包含错误信息的元素。
ui-state-disabled 将禁用的样式应用于元素(但实际上并不禁用元素本身)。

清单 35-9 显示了高亮和禁用提示的使用。

清单 35-9 。使用 jQuery UI 高亮显示类

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        body > div {float: left; margin: 10px}
        span {padding: 10px; display: block}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div class="ui-widget">
        <div class="ui-widget-header ui-corner-top" style="padding-left: 5px">
            Flowers
        </div>
        <div class="ui-widget-content ui-corner-bottom">
            <div class="dcell">
                <img src="peony.png"/><label for="peony">Peony:</label>
                <input name="peony" value="0" />
            </div>
        </div>
    </div>

    <div class="ui-widgetui-state-highlight ui-corner-all">
        <div class="ui-widget-header ui-corner-top" style="padding-left: 5px">
            Flowers
        </div>
        <div class="ui-widget-content ui-corner-bottom">
            <div class="dcell">
                <img src="peony.png"/><label for="peony">Peony:</label>
                <input name="peony" value="0" />
            </div>
        </div>
    </div>

    <div class="ui-widgetui-state-disabled">
        <div class="ui-widget-header ui-corner-top" style="padding-left: 5px">
            Flowers
        </div>
        <div class="ui-widget-content ui-corner-bottom">
            <div class="dcell">
                <img src="peony.png"/><label for="peony">Peony:</label>
                <input name="peony" value="0" />
            </div>
        </div>
    </div>
</body>
</html>

你可以在图 35-6 中看到这些类的效果。注意,在使用ui-state-highlight类时,我也应用了ui-corner-all样式。这个类应用了一个边框,默认情况下显示为方形的角。如果子元素有圆角,那么您也需要将突出显示的元素圆角化。

9781430263883_Fig35-06.jpg

图 35-6 。应用突出显示提示类

清单 35-10 显示了错误状态的使用。

清单 35-10 。使用错误提示

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        body > div {float: left; margin: 10px; padding: 20px}
    </style>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <div class="ui-state-error">
        Oops! Something went wrong.
    </div>
</body>
</html>

你可以在图 35-7 中看到效果。

9781430263883_Fig35-07.jpg

图 35-7 。使用错误提示类

摘要

在这一章中,我描述了 jQuery UI 为颜色、可见性的动画转换提供的增强。和 CSS 类。这些都是有用的特性,但是必须小心使用,避免给用户带来干扰和恼人的效果。我还描述了 jQuery UI CSs 框架的主要类,它允许我们以与 jQuery UI 小部件一致的方式设计元素,允许我们将 jQuery UI 主题的外观扩展到 HTML 文档的其余部分。

三十六、使用延迟对象

在本书中,你已经看到了依赖于回调的例子——你提供一个函数,当事情发生时执行这个函数。一个很好的例子就是处理事件的方式,使用像click这样的方法,将一个函数作为参数传入。在用户触发事件之前,函数中的代码语句不会被执行——直到那时,我们的函数才处于休眠状态。

延迟对象是 jQuery 术语,指的是对回调使用方式的一系列增强。当使用延迟对象时,回调可以用于任何情况,而不仅仅是事件;它们提供了许多选项,并控制何时以及如何执行回调函数。

在本章中,我将从一个相当简单的例子开始,然后在此基础上展示管理延迟对象和后台任务的特性和一些有用的模式。

我说合理简单,因为使用延迟对象将我们带入了异步并行编程的世界。有效的并行编程是一项很难掌握的技能,JavaScript 使它变得更加困难,因为它缺少其他语言(如 Java 和 C#)中存在的一些高级特性。大多数项目不需要使用延迟对象,如果您是并行编程的新手,我建议您跳过这一章,直到您正在从事一个需要延迟对象的项目。表 36-1 提供了本章的总结。

表 36-1 。章节总结

问题 解决办法 列表
使用延迟对象的基本功能。 使用 done 方法注册一个callback函数。调用resolve方法来触发回调。 one
对后台任务使用延迟对象。 使用setTimeout函数创建一个后台任务,并在任务完成时调用resolve方法。 2-4
表示任务失败。 使用reject方法触发使用fail方法注册的处理程序。 5, 6
在单个方法调用中注册两个延迟对象结果的处理程序。 使用then方法。 seven
指定一个将被执行的函数,不管被延迟的对象是resolved还是rejected 使用always方法。 eight
对相同的结果使用多个回调。 多次调用注册方法或以逗号分隔的参数形式传递函数。 nine
创建一个延迟对象,其结果由其他延迟对象的结果决定。 使用when方法。 Ten
发出任务进度信号。 调用notify方法,这将触发已经使用progress方法注册的回调处理程序。 11, 12
获取有关延迟对象状态的信息。 使用state方法。 Thirteen
使用 Ajax 承诺。 像对待延迟对象一样对待来自 jQuery Ajax 方法的响应。 Fourteen

第一延迟对象示例

我将从展示延迟对象如何工作开始,然后展示如何使用它们。清单 36-1 是一个包含延迟对象的简单例子。

清单 36-1 。一个简单的延迟对象示例

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        td {text-align: left; padding: 5px}
        table {width: 200px; border-collapse: collapse; width: 50%; float: left}
        #buttonDiv {width: 15%; text-align: center; margin: 20px; float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {
            var def = $.Deferred();

            def.done(function() {
                displayMessage("Callback Executed");
            })

            $("button").button().click(function() {
                def.resolve();
            })

            displayMessage("Ready")
        })

        function displayMessage(msg) {
            $("tbody").append("<tr><td>" + msg + "</td></tr>")
        }
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <table class="ui-widget" border=1>
        <thead class="ui-widget-header">
            <tr><th>Message</th></tr>
        </thead>
        <tbody class="ui-widget-content">
        </tbody>
    </table>

    <div id="buttonDiv">
        <button>Go</button>
    </div>
</body>
</html>

这是一个延迟对象如何工作的简单演示。我将一步一步地介绍它,为本章的其余部分设置背景。首先,我通过调用$.Deferred方法创建了一个延迟对象,如下所示:

...
var def = $.Deferred();
...

Deferred方法返回一个延迟对象——我已经将这个对象赋给了名为def的变量。延迟对象都是关于回调的,所以我的下一步是使用done方法向延迟对象注册一个函数,就像这样:

...
def.done(function() {
    displayMessage("Callback Executed");
})
...

当它被执行时,回调函数将调用displayMessage函数,该函数向文档中的table元素添加一行。

最后一步是进行设置,这样我就可以触发回调函数,这是通过调用resolve方法来完成的。像这样触发回调被称为解析延迟对象。我希望能够控制延迟对象何时被解析,所以我在文档中添加了一个按钮,并使用click方法来处理一个事件。具有讽刺意味的是,我使用一种回调机制来帮助描述另一种机制——出于本章的目的,我希望您忽略事件系统,并关注这样一个事实,即延迟的对象在按钮被按下之前不会被解析。下面是调用resolve并触发用done方法注册的回调函数的函数:

...
$("button").button().click(function() {
    def.resolve();
})
...

在 resolve 方法被调用之前,被延迟的对象保持未被解析,并且我们的回调函数不会被执行。按下按钮解析被延迟的对象,执行回调,并在表格中显示消息,如图图 36-1 所示。

9781430263883_Fig36-01.jpg

图 36-1 。解析延迟的对象

这里需要理解的重要一点是,延迟对象没有做任何特殊的事情。我们使用done方法注册了一个回调函数,在调用resolve方法之前它们不会被执行。在这个例子中,延迟的对象直到按钮被点击后才被解析,这时回调函数被执行,一条新消息被添加到table元素中。

理解延迟对象为什么有用

当您希望在某个任务结束时执行函数,而不必直接监视该任务时,延迟对象非常有用——尤其是当该任务正在后台执行时。清单 36-2 包含了一个演示,我将开始修改它以增加特性。

清单 36-2 。对长期任务使用回调

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

        def.done(function() {
            displayMessage("Callback Executed");
        })

        function performLongTask() {

            var start = $.now();

            var total = 0;
            for (var i = 0; i < 500000000 ; i++) {
                total += i;
            }
            var elapsedTime = (($.now() - start)/1000).toFixed(1)
            displayMessage("Task Complete. Time: " + elapsedTime + " sec")
            def.resolve();
        }

        $("button").button().click(function() {
            displayMessage("Calling performLongTask()")
            performLongTask()
            displayMessage("performLongTask() Returned")
        })

        displayMessage("Ready")
    })

    function displayMessage(msg) {
        $("tbody").append("<tr><td>" + msg + "</td></tr>")
    }
</script>
...

本例中的过程是由performLongTask函数定义的,它将一系列数字加在一起——我想要一些简单的东西,只需几秒钟就能完成,这符合要求。

image 提示在我的系统上,performLongTask函数中的for循环大约需要 0.5 秒才能完成,但是您可能需要调整循环的上限,以便在您的系统上获得类似的结果。对于这些例子来说,4-5 秒是一个很好的持续时间,足够演示延迟对象的特性,但又不至于长到让您有时间在等待任务完成的同时煮咖啡。

现在点击按钮调用performLongTask功能。该函数在其工作完成时调用延迟对象的resolve方法,从而导致回调函数被调用。performLongTask函数在调用resolve之前将自己的消息添加到table元素中,因此我们可以通过脚本看到进程的顺序。你可以在图 36-2 中看到结果。

9781430263883_Fig36-02.jpg

图 36-2 。使用延迟对象观察任务完成情况

这是一个同步任务的例子。你按下按钮,然后等待每个被调用的函数完成。当performLongTask功能工作时,Go按钮保持按下状态的方式是同步工作的最佳标志。决定性的证据来自于图 36-2 中显示的消息序列——来自点击事件处理程序的消息出现在来自performLongTask和回调函数的消息之前和之后。

延迟对象的主要好处来自于处理异步任务——在后台执行的任务。你不希望用户界面像上一个例子那样被锁住;因此,取而代之的是,您在后台启动任务,密切关注它们,并更新文档以向用户提供有关工作进度和结果的信息。

启动后台任务最简单的方法是使用setTimeout函数,这意味着使用另一种回调机制。这可能看起来有点奇怪,但是 JavaScript 缺少管理异步任务的语言工具,而其他语言都是用这种工具设计的,所以我们只能用那些可用的功能来凑合。清单 36-3 显示了修改后的例子,使得performLongTask函数中耗时的部分在后台完成。

清单 36-3 。异步执行工作

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

        def.done(function() {
            displayMessage("Callback Executed");
        })

        function performLongTask() {
            setTimeout(function() {
                var start = $.now();

                var total = 0;
                for (var i = 0; i < 500000000 ; i++) {
                    total += i;
                }
                var elapsedTime = (($.now() - start)/1000).toFixed(1)
                displayMessage("Task Complete. Time: " + elapsedTime + " sec")
                def.resolve();
            }, 10);
        }

        $("button").button().click(function() {
            displayMessage("Calling performLongTask()")
            performLongTask()
            displayMessage("performLongTask() Returned")
        })

        displayMessage("Ready")
    })

    function displayMessage(msg) {
        $("tbody").append("<tr><td>" + msg + "</td></tr>")
    }
</script>
...

我使用setTimeout函数在 10 毫秒的延迟后执行performLongTask函数中的for循环。你可以在图 36-3 中看到它的效果——注意来自点击处理函数的消息出现在来自performLongTask和回调函数的消息之前。如果您自己运行这个示例,您会注意到按钮会立即弹回正常状态,而不是等待工作完成。

9781430263883_Fig36-03.jpg

图 36-3 。在后台执行任务

回调在处理后台任务时尤为重要,因为你不知道它们何时完成。你可以建立自己的信号系统——例如,更新一个变量——但是你需要为每一个执行的后台任务做这件事,这很快就会变得令人厌倦并且容易出错。延迟对象提供了一种标准化的机制来指示任务已经完成,正如我将在后面的例子中演示的,它们在如何完成任务方面提供了很大的灵活性。

整理示例

在我开始深入研究延迟对象的特性之前,我将更新这个例子以使用我在实际项目中使用的模式。这纯粹是个人喜好,但是我喜欢将工作负载从异步包装器中分离出来,并将延迟对象的产生集成到函数中。清单 36-4 显示了这些变化。

清单 36-4 。整理示例

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        td {text-align: left; padding: 5px}
        table {width: 200px; border-collapse: collapse; float: left; width: 300px}
        #buttonDiv {text-align: center; margin: 20px; float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            function performLongTaskSync() {
                var start = $.now();

                var total = 0;
                for (var i = 0; i < 500000000 ; i++) {
                    total += i;
                }
                var elapsedTime = (($.now() - start)/1000).toFixed(1)
                displayMessage("Task Complete. Time: " + elapsedTime + " sec")
                return total;
            }

            function performLongTask() {
                return $.Deferred(function(def) {
                    setTimeout(function() {
                        performLongTaskSync();
                        def.resolve();
                    }, 10)
                })
            }

            $("button").button().click(function() {
                if ($(":checked").length > 0) {
                    displayMessage("Calling performLongTask()")
                    var observer = performLongTask();
                    observer.done(function() {
                        displayMessage("Callback Executed");
                    });
                    displayMessage("performLongTask() Returned")
                } else {
                    displayMessage("Calling performLongTaskSync()")
                    performLongTaskSync();
                    displayMessage("performLongTaskSync() Returned")
                }
            })

            $(":checkbox").button();
            displayMessage("Ready")
        })

        function displayMessage(msg) {
            $("tbody").append("<tr><td>" + msg + "</td></tr>")
        }
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <table class="ui-widget" border=1>
        <thead class="ui-widget-header">
            <tr><th>Message</th></tr>
        </thead>
        <tbody class="ui-widget-content">
        </tbody>
    </table>

    <div id="buttonDiv">
        <button>Go</button>
        <input type="checkbox" id="async" checked>
        <label for="async">Async</label>
    </div>
</body>
</html>

在这个例子中,我将工作负载分解成一个名为performLongTasksync 的函数,它只负责执行计算。它不知道后台任务或回调函数。我喜欢将工作负载分开,因为这使得在开发的早期阶段测试代码更加容易。下面是同步功能:

...
function performLongTaskSync() {
    var start = $.now();

    var total = 0;
    for (var i = 0; i < 500000000 ; i++) {
        total += i;
    }
    var elapsedTime = (($.now() - start)/1000).toFixed(1)
    displayMessage("Task Complete. Time: " + elapsedTime + " sec")
    return total;
}
...

我已经分离出异步执行任务的代码——这是在performLongTask函数中,它是围绕performLongTasksync函数的异步包装器,当工作完成时,它使用一个延迟对象来触发回调。下面是修改后的performLongTask函数:

...
function performLongTask() {
    return $.Deferred(function(def) {
        setTimeout(function() {
            performLongTaskSync();
            def.resolve();
        }, 10)
    })
}
...

如果我将一个函数传递给Deferred方法,它会在对象创建后立即执行,并且函数会被传递一个新的延迟对象作为参数。使用这个特性,我可以创建一个简单的包装器函数,它异步执行工作,并在工作完成时触发回调。

image 提示如果你很细心,你会注意到有可能在任务完成并且调用了resolve方法之后,调用done方法来注册回调函数。这可能发生在短任务中,但是回调函数仍然会被调用,即使在resolve之后done被调用。

我喜欢创建这样的包装器的另一个原因是,一旦被延迟的对象被解析或拒绝,它们就不能被重置(我马上解释拒绝)。通过在包装器函数中创建延迟对象,我确保我总是使用新鲜的、未解析的延迟对象。

我对这个例子做的另一个改变是添加了一个切换按钮,它允许任务同步或异步执行。我将在以后的例子中去掉这个特性,因为这是关于异步任务的一章,但是这是一个很好的方法来确保你对这种差异感到满意。你可以在图 36-4 中看到两种模式的输出。

9781430263883_Fig36-04.jpg

图 36-4 。同步和异步执行相同的任务

使用其他回调

现在我已经有了一个基本的异步例子,我可以转向延迟对象提供的一些有用的特性。首先,我可以从我们的任务中发出不同结果的信号。表 36-2 描述了可用于注册回调的方法以及在触发它们的延迟对象上调用的方法。我已经解释了doneresolve方法,我将在接下来的章节中介绍其他方法。

表 36-2 。注册回调的方法

回拨注册方法 由...触发
done resolve
fail reject
always resolvereject

拒绝延期对象

并非所有任务都成功完成。当它们这样做时,我通过调用resolve方法解析延迟的对象。但是当出现问题时,我们使用reject方法拒绝延迟的对象。使用fail方法为失败的任务注册回调函数。与resolve方法触发用done注册的回调相同,reject方法触发用fail方法注册的回调。清单 36-5 显示了一个任务,它将解析或者拒绝它的延迟对象。

清单 36-5 。拒绝延期对象

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        td {text-align: left; padding: 5px}
        table {width: 200px; border-collapse: collapse; float: left; width: 300px}
        #buttonDiv {text-align: center; margin: 20px; float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            function performLongTaskSync() {
                var start = $.now();

                var total = 0;
                for (var i = 0; i < 5000000  ; i++) {
                    total += (i + Number((Math.random() + 1).toFixed(0)));
                }
                var elapsedTime = (($.now() - start)/1000).toFixed(1)
                displayMessage("Task Complete. Time: " + elapsedTime + " sec")
                return total;
            }

            function performLongTask() {
                return $.Deferred(function(def) {
                    setTimeout(function() {
                        var total = performLongTaskSync();
                        if (total % 2 == 0) {
                            def.resolve(total);
                        } else {
                            def.reject(total);
                        }
                    }, 10)})
            }

            $("button").button().click(function() {
                displayMessage("Calling performLongTask()")
                var observer = performLongTask();
                displayMessage("performLongTask() Returned")
                observer.done(function(total) {
                    displayMessage("Done Callback Executed: " + total);
                });
                observer.fail(function(total) {
                    displayMessage("Fail Callback Executed: " + total);
                });
            })

            displayMessage("Ready")
        })

        function displayMessage(msg) {
            $("tbody").append("<tr><td>" + msg + "</td></tr>")
        }
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <table class="ui-widget" border=1>
        <thead class="ui-widget-header">
            <tr><th>Message</th></tr>
        </thead>
        <tbody class="ui-widget-content">
        </tbody>
    </table>

    <div id="buttonDiv">
        <button>Go</button>
    </div>
</body>
</html>

在这个例子中,我调整了任务,这样在for循环的每次迭代中,一个小的随机数被添加到总数中。异步包装函数performLongTask检查同步函数返回的总数,如果总数是偶数,则解析延迟对象。如果总数是奇数,那么performLongTask函数拒绝延迟的对象,如下所示:

...
if (total % 2 == 0) {
    def.resolve(total);
} else {
    def.reject(total);
}
...

在调用了performLongTask函数之后,我的click事件处理程序使用donefail方法为两个结果注册了回调函数,如下所示:

...
var observer = performLongTask();
displayMessage("performLongTask() Returned")
observer.done(function(total) {
    displayMessage("Done Callback Executed: " + total);
});
observer.fail(function(total) {
    displayMessage("Fail Callback Executed: " + total);
});
...

注意,当我调用resolvereject方法时,我会将参数传递给它们。您不必将参数传递给这些方法,但是如果您这样做了,您提供的对象将作为参数传递给回调函数,这允许您提供关于发生了什么的附加上下文或细节。在这个例子中,任务的状态由计算总数决定,我已经将这个总数作为参数传递给了donereject方法。你可以在图 36-5 中看到一个被解决和拒绝的延期对象的结果。

9781430263883_Fig36-05.jpg

图 36-5 。一项可能成功也可能失败的任务

链接延迟对象方法调用

延迟对象方法是可链接的,这意味着每个方法返回一个延迟对象,其他方法可以在其上调用。这是我在这本书里一直在用 jQuery 对象做的事情。清单 36-6 展示了如何将对donefail方法的调用链接在一起。

清单 36-6 。链接延迟对象方法调用

...
$("button").button().click(function() {
    performLongTask().done(function(total) {
        displayMessage("Done Callback Executed: " + total);
    }).fail(function(total) {
        displayMessage("Fail Callback Executed: " + total);
    });
})
...

涵盖两种结果

如果每个结果都有回调,那么可以使用then方法一次性注册它们。第一个参数是解析延迟对象时使用的回调,第二个参数是拒绝延迟对象时使用的回调。清单 36-7 显示了正在使用的then方法。

清单 36-7 。使用 Then 方法

...
$("button").button().click(function() {
    displayMessage("Calling performLongTask()")
    var observer = performLongTask();
    displayMessage("performLongTask() Returned")

    observer.then(
        function(total) {
            displayMessage("Done Callback Executed");
        },
        function(total) {
            displayMessage("Fail Callback Executed");
        }
    );
})
...

我倾向于使用方法链接,因为我发现它产生的代码中每个函数准备处理的结果更明显。

使用结果无关的回调

有些时候,不管任务的结果如何,您都希望执行一个回调函数。一种常见的模式是使用always方法注册一个函数,该函数删除或隐藏指示正在执行某个后台任务的元素,并使用donefail方法向用户显示接下来的步骤。清单 36-8 显示了使用always方法注册一个函数,该函数的行为与任务结果无关。

清单 36-8 。使用 Always 方法注册与结果无关的函数

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        td {text-align: left; padding: 5px}
        table {width: 200px; border-collapse: collapse; float: left; width: 300px}
        #buttonDiv {text-align: center; margin: 20px; float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            function performLongTaskSync() {
                var start = $.now();

                var total = 0;
                for (var i = 0; i < 5000000  ; i++) {
                    total += (i + Number((Math.random() + 1).toFixed(0)));
                }
                var elapsedTime = (($.now() - start)/1000).toFixed(1)
                displayMessage("Task Complete. Time: " + elapsedTime + " sec")
                return total;
            }

            function performLongTask() {
                return $.Deferred(function(def) {
                    setTimeout(function() {
                        var total = performLongTaskSync();
                        if (total % 2 == 0) {
                            def.resolve(total);
                        } else {
                            def.reject(total);
                        }
                    }, 10)})
            }

            $("button").button().click(function() {
                displayMessage("Calling performLongTask()")
                var observer = performLongTask();
                displayMessage("performLongTask() Returned")

                $("#dialog").dialog("open");

                observer.always(function() {
                    $("#dialog").dialog("close");
                });

                observer.done(function(total) {
                    displayMessage("Done Callback Executed: " + total);
                });
                observer.fail(function(total) {
                    displayMessage("Fail Callback Executed: " + total);
                });

            })

            $("#dialog").dialog({
                autoOpen: false,
                modal: true

            })

            displayMessage("Ready")
        })

        function displayMessage(msg) {
            $("tbody").append("<tr><td>" + msg + "</td></tr>")
        }
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <table class="ui-widget" border=1>
        <thead class="ui-widget-header">
            <tr><th>Message</th></tr>
        </thead>
        <tbody class="ui-widget-content">
        </tbody>
    </table>

    <div id="buttonDiv">
        <button>Go</button>
    </div>

    <div id="dialog">
        Performing Task...
    </div>
</body>
</html>

在本例中,我添加了一个 jQuery UI 模式对话框,当任务运行时会显示该对话框。我使用always方法来注册一个函数,该函数在任务完成时关闭对话框——这意味着我不必在处理已解析或已拒绝的延迟对象的函数中复制任务完成后进行整理的代码。

image 提示回调函数按照向延迟对象注册的顺序被调用。在这个例子中,我在调用donefail方法之前调用了 always 方法,这意味着与结果无关的函数总是在处理已解决或已拒绝结果的函数之前被调用。

使用多个回调

使用延迟对象的好处之一是,我们可以将代码分割成处理特定活动的小函数。为了允许进一步分解我们的代码,延迟对象提供了为同一结果注册多个回调的支持。清单 36-9 提供了一个演示。

清单 36-9 。向延迟对象注册多个回调函数

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

        function performLongTaskSync() {
            var start = $.now();

            var total = 0;
            for (var i = 0; i < 5000000  ; i++) {
                total += (i + Number((Math.random() + 1).toFixed(0)));
            }
            var elapsedTime = (($.now() - start)/1000).toFixed(1)
            displayMessage("Task Complete. Time: " + elapsedTime + " sec")
            return total;
        }

        function performLongTask() {
            return $.Deferred(function(def) {
                setTimeout(function() {
                    var total = performLongTaskSync();
                    if (total % 2 == 0) {
                        def.resolve({
                            total: total
                        });
                    } else {
                        def.reject(total);
                    }
                }, 10)})
        }

        $("button").button().click(function() {
            displayMessage("Calling performLongTask()")
            var observer = performLongTask();
            displayMessage("performLongTask() Returned")

            $("#dialog").dialog("open");

            observer.done(function(data) {
                data.touched = 1;
                displayMessage("1st Done Callback Executed");
            });

            observer.done(function(data) {
                data.touched++;
                displayMessage("2nd Done Callback Executed");
            }, function(data) {
                data.touched++;
                displayMessage("3rd Done Callback Executed");
            });

            observer.done(function(data) {
                displayMessage("4th Done Callback Executed: " + data.touched);
            });

            observer.fail(function(total) {
                displayMessage("Fail Callback Executed: " + total);
            });

            observer.always(function() {
                displayMessage("Always Callback Executed");
                $("#dialog").dialog("close");
            });

        })

        $("#dialog").dialog({
            autoOpen: false,
            modal: true
        })

        displayMessage("Ready")
    })

    function displayMessage(msg) {
        $("tbody").append("<tr><td>" + msg + "</td></tr>")
    }
</script>
...

在这个例子中,我使用done方法注册了四个回调函数。如代码所示,我们可以通过向注册方法传递多个函数(用逗号分隔)来单独或成组注册函数。延迟对象确保回调函数按照注册的顺序执行。

请注意,我已经更改了本例中传递给resolve方法的参数,使计算结果成为 JavaScript 对象中的一个属性。我这样做是为了演示回调函数能够修改通过延迟对象传递的数据。这对于在处理程序函数之间提供简单的通信(声明已经采取了某些特定的动作)非常有用。你可以在图 36-6 中看到拥有多个处理器的效果。

9781430263883_Fig36-06.jpg

图 36-6 。对同一结果使用多个回调

image 提示通过传递函数数组作为参数,你可以使用 then 方法为每个结果指定多个回调。

使用多个延迟对象的结果

我们可以使用when方法来创建延迟对象,其结果是从其他几个延迟对象中派生出来的。当我们依赖于几个后台任务的结果时,或者当我们不想开始一个任务,直到我们确信一组其他任务已经实现了特定的结果时,这种技术是有用的。清单 36-10 提供了一个演示。

清单 36-10 。使用 When 方法

...
$("button").button().click(function() {

    var ob1 = performLongTask()
        .done(function() {
            displayMessage("1st Task <b>Resolved</b>")
        })
        .fail(function() {
            displayMessage("1st Task <b>Failed</b>")
        })

    var ob2 = performLongTask()
        .done(function() {
            displayMessage("2nd Task <b>Resolved</b")
        })
        .fail(function() {
            displayMessage("2nd Task <b>Failed</b>")
        })

    var ob3 = performLongTask()
        .done(function() {
            displayMessage("3rd Task <b>Resolved</b>")
        })
        .fail(function() {
            displayMessage("3rd Task <b>Failed</b>")
        })

    $.when(ob1, ob2, ob3)
        .done(function() {
            displayMessage("Aggregate <b>Resolved</b>")
        })
        .fail(function() {
            displayMessage("Aggregate <b>Failed</b>")
        })
})
...

在这个例子中,我有三个延迟对象,每个对象都是通过调用performLongTask函数创建的,并且我已经使用donefail方法给它们附加了回调函数。

我已经将所有三个延迟对象传递给了when方法,该方法返回另一个延迟对象(称为聚合延迟对象)。我已经用普通的donefail方法给集合附加了回调函数。聚合的结果由其他三个延迟对象的结果决定。如果所有的三个常规延迟对象都被解析,那么聚集也被解析,并且done函数将被调用。然而,如果常规延迟对象中的任何一个被拒绝,那么聚集也被拒绝,并且 ?? 函数将被调用。您可以在图 36-7 中看到聚合的两种结果。

9781430263883_Fig36-07.jpg

图 36-7 。使用 when 方法

image 注意如果仔细观察图中的消息序列,您会发现一个时序异常。一旦任何底层对象被拒绝,聚集延迟对象就会被拒绝——这意味着用 fail 方法注册的回调函数可以在仍有任务运行时被触发。在处理被拒绝的聚合对象时,您不能假定它所依赖的所有任务都已完成。

提供进度信息

在后台执行长期任务时,向用户提供进度信息通常是个好主意。延迟对象可以用来将进度信息从任务传递给回调函数,就像我们传递结果信息一样。我们使用notify方法生成进度信息,并使用progress方法注册我们的回调函数。清单 36-11 包含了一个例子。

清单 36-11 。通过延迟对象生成和使用进度信息

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

        function performLongTaskSync() {
            var total = 0;
            for (var i = 0; i < 5000000  ; i++) {
                total += (i + Number((Math.random() + 1).toFixed(0)));
            }
            return total;
        }

        function performLongTask() {
            return $.Deferred(function(def) {
                setTimeout(function() {
                    var progressValue = 0;
                    for (var i = 0; i < 4; i++) {
                        performLongTaskSync();
                        progressValue += 25;
                        def.notify(progressValue)
                    }
                    def.resolve();
                }, 10)}
            )
        }

        $("button").button().click(function() {

            performLongTask().progress(function(val) {
                displayMessage("Progress: " + val + "%")
            }).done(function() {
                displayMessage("Task Resolved");
            })
        })

        $("#dialog").dialog({
            autoOpen: false,
            modal: true
        })

        displayMessage("Ready")
    })

    function displayMessage(msg) {
        $("tbody").append("<tr><td>" + msg + "</td></tr>")
    }
</script>
...

在本例中,任务是执行四次计算。在每次计算之后,我对延迟的对象调用notify方法,并传入我的进度百分比(尽管您可以传递任何对您的 web 应用有意义的对象或值——为了简单起见,我使用百分比)。在click处理函数中,我使用了progress方法来注册一个函数,该函数将被调用以响应进度更新——我使用该函数向文档中的table添加一条消息。

这个例子演示了提供进度信息的基本能力,但是它并没有按照我们希望的方式工作。问题是浏览器直到所有四次迭代完成后才获得用新行更新 DOM 的更改——这是 JavaScript 任务管理方式的一个方面,意味着我们在任务结束时一次性获得所有的进度更新。为了解决这个问题,我们需要在任务的每个阶段之间添加一些小的延迟,以便给浏览器提供执行更新所需的时间。清单 36-12 展示了我们如何使用setTimeout函数来引入这些延迟并创建一个延迟对象链。我通常会使用一个for循环来设置延迟和被延迟的对象,但是为了让这个例子更容易阅读,我已经明确定义了所有的步骤。

清单 36-12 。分解任务以允许 DOM 更改

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

        function performLongTaskSync() {
            var total = 0;
            for (var i = 0; i < 5000000  ; i++) {
                total += (i + Number((Math.random() + 1).toFixed(0)));
            }
            return total;
        }

        function performLongTask() {

            function doSingleIteration() {
                return $.Deferred(function(innerDef) {
                        setTimeout(function() {
                        performLongTaskSync();
                        innerDef.resolve();
                    }, 10)
                })
            }

            var def = $.Deferred();

            setTimeout(function() {

                doSingleIteration().done(function() {
                    def.notify(25);
                    doSingleIteration().done(function() {
                        def.notify(50);
                        doSingleIteration().done(function() {
                            def.notify(75);
                            doSingleIteration().done(function() {
                                def.notify(100);
                                def.resolve();
                            })
                        })
                    })
                })
            }, 10);

            return def;
        }

        $("button").button().click(function() {

            performLongTask().progress(function(val) {
                displayMessage("Progress: " + val + "%")
            }).done(function() {
                displayMessage("Task Resolved");
            })
        })

        $("#dialog").dialog({
            autoOpen: false,
            modal: true
        })

        displayMessage("Ready")
    })

    function displayMessage(msg) {
        $("tbody").append("<tr><td>" + msg + "</td></tr>")
    }
</script>
...

通过这一更改,可以正确显示进度更新。可以看到图 36-8 所示的更新。

9781430263883_Fig36-08.jpg

图 36-8 。使用延迟对象提供进度信息

获取有关延迟对象的信息

延迟对象定义了state方法,我们可以用它来建立对象的状态,也就是正在执行的任务。该方法可返回的值在表 36-3 中描述。

表 36-3 。状态对象的值

价值 描述
pending 没有对延迟对象调用过resolvereject方法。
resolved 被延迟的对象已经被解析(使用resolve方法)。
rejected 被延迟的对象已被拒绝(使用rejected方法)。

image 小心使用这种方法时要小心。特别是,如果您发现自己在轮询一个延迟对象的状态,您应该停下来想一想——您的 web 应用可能有一些设计问题。轮询状态,尤其是在whilefor循环中,可能意味着您已经有效地使您的任务同步,同时招致与异步任务相关的开销和复杂性。

我发现state方法唯一有用的时候是当我使用 always 方法注册了一个回调,并且我对任务的结果感兴趣的时候。一般来说,我在单独的回调函数中使用donefail方法,但是有时候我的代码对于两种结果来说很大程度上是相同的,但并不完全相同。清单 36-13 包含了一个使用state方法的演示。

清单 36-13 。使用状态方法

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        td {text-align: left; padding: 5px}
        table {width: 200px; border-collapse: collapse; float: left; width: 300px}
        #buttonDiv {text-align: center; margin: 20px; float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            function performLongTaskSync() {
                var start = $.now();

                var total = 0;
                for (var i = 0; i < 5000000  ; i++) {
                    total += (i + Number((Math.random() + 1).toFixed(0)));
                }
                var elapsedTime = (($.now() - start)/1000).toFixed(1)
                displayMessage("Task Complete. Time: " + elapsedTime + " sec")
                return total;
            }

            function performLongTask() {
                return $.Deferred(function(def) {
                    setTimeout(function() {
                        var total = performLongTaskSync();
                        if (total % 2 == 0) {
                            def.resolve(total);
                        } else {
                            def.reject(total);
                        }
                    }, 10)})
            }

            $("button").button().click(function() {
                displayMessage("Calling performLongTask()")
                var observer = performLongTask();
                displayMessage("performLongTask() Returned")

                $("#dialog").dialog("open");

                observer.always(function() {
                    if (observer.state() == "resolved") {
                        $("#dialog").dialog("close");
                    } else {
                        $("#dialog").text("Error!")
                    }
                });

                observer.done(function(total) {
                    displayMessage("Done Callback Executed: " + total);
                });
                observer.fail(function(total) {
                    displayMessage("Fail Callback Executed: " + total);
                });

            })

            $("#dialog").dialog({
                autoOpen: false,
                modal: true

            })

            displayMessage("Ready")
        })

        function displayMessage(msg) {
            $("tbody").append("<tr><td>" + msg + "</td></tr>")
        }
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <table class="ui-widget" border=1>
        <thead class="ui-widget-header">
            <tr><th>Message</th></tr>
        </thead>
        <tbody class="ui-widget-content">
        </tbody>
    </table>

    <div id="buttonDiv">
        <button>Go</button>
    </div>

    <div id="dialog">
        Performing Task...
    </div>
</body>
</html>

使用 Ajax 延迟对象

也许延迟对象功能最有用的方面是它被合并到 Ajax 的 jQuery 支持中的方式(我在第十四章和第十五章中描述了这一点)。我们从ajaxgetJSON等方法中获取的jxXHR对象实现了Promise接口,该接口为我们提供了一个由常规延迟对象定义的方法子集。一个Promise定义了donefailthen,always方法,并且可以与when方法一起使用。清单 36-14 显示了我们如何混合和匹配 Ajax 承诺和延迟对象。

清单 36-14 。使用 Ajax 承诺和延迟对象

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="jquery-ui-1.10.3.custom.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <link rel="stylesheet" type="text/css" href="jquery-ui-1.10.3.custom.css"/>
    <style type="text/css">
        td {text-align: left; padding: 5px}
        table {width: 200px; border-collapse: collapse; float: left; width: 300px}
        #buttonDiv {text-align: center; margin: 20px; float: left}
    </style>
    <script type="text/javascript">
        $(document).ready(function() {

            function performLongTaskSync() {
                var start = $.now();

                var total = 0;
                for (var i = 0; i < 5000000  ; i++) {
                    total += (i + Number((Math.random() + 1).toFixed(0)));
                }
                var elapsedTime = (($.now() - start)/1000).toFixed(1)
                displayMessage("Task Complete. Time: " + elapsedTime + " sec")
                return total;
            }

            function performLongTask() {
                return $.Deferred(function(def) {
                    setTimeout(function() {
                        performLongTaskSync();
                        def.resolve();
                    }, 10)})
            }

            $("button").button().click(function() {
                displayMessage("Calling performLongTask()")
                var observer = performLongTask().done(function() {
                    displayMessage("Task complete")
                });
                displayMessage("performLongTask() Returned")

                displayMessage("Calling getJSON()")
                var ajaxPromise = $.getJSON("mydata.json").done(function() {
                    displayMessage("Ajax Request Completed")
                });
                displayMessage("getJSON() Returned")

                $.when(observer, ajaxPromise).done(function() {
                    displayMessage("All Done");
                })
            })
            displayMessage("Ready")
        })

        function displayMessage(msg) {
            $("tbody").append("<tr><td>" + msg + "</td></tr>")
        }
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>

    <table class="ui-widget" border=1>
        <thead class="ui-widget-header">
            <tr><th>Message</th></tr>
        </thead>
        <tbody class="ui-widget-content">
        </tbody>
    </table>

    <div id="buttonDiv">
        <button>Go</button>
    </div>
</body>
</html>

image 提示您可以通过调用延迟对象上的 promise 方法来创建自己的 Promise 对象。如果您正在编写一个 JavaScript 库,并且您只希望允许其他程序员附加回调,而不希望解析或拒绝您的延迟对象,这将非常有用。

在这个例子中,我使用了getJSON方法,并像对待延迟对象一样处理结果。我使用done方法附加了一个回调函数,并将它用作when方法的参数。你可以在图 36-9 的中看到这个例子的输出。

9781430263883_Fig36-09.jpg

图 36-9 。使用 Ajax 承诺

摘要

在这一章中,我已经演示了 jQuery 延迟对象特性,它让我们能够显示任务的进度和结果,通常是在后台执行的任务。在 jQuery 对 Ajax 的支持中使用了延迟对象,这让我们能够以一致的方式处理 Ajax 请求和自定义后台任务。延迟对象是一个高级特性,大多数 web 应用不需要它们——但是对于那些执行重要后台任务的项目,它们可以帮助用户保持响应体验。

posted @ 2024-08-19 17:29  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报