使用 jQuery,第 2 部分: 构建未来的 Web 应用程序

事件、属性和 CSS

这个关于 jQuery 系列文章 的第二篇介绍了如何将更多的交互添加到 Web 站点,以创建动态的富 Internet 应用程序(RIA)。通过本文,了解 jQuery 如何综合利用用户交互生成的事件、来自 Web 站点的信息,以及更改应用程序外观的功能(无需重新加载),来轻松迅速地创建 RIA。

Michael Abernethy, 产品开发经理, Optimal Auctions

在 Michael Abernethy 的 10 年技术生涯中,他与各种不同的技术和客户机打交道。他目前是一家拍卖软件公司 Optimal Auctions 的产品开发经理。他现在专注于富 Internet 应用程序,试图同时实现应用程序的复杂性和简单性。他在空闲时常常带上自己最喜欢的书去墨西哥湾海滩度假。



2008 年 10 月 10 日

简介

这 6 个月以来,JQuery 受欢迎的指数迅速攀升,现已成为 Web 开发人员首选的 JavaScript 库。与此同时,人们对富 Internet 应用程序(Rich Internet Application,RIA)的应用和需求也在迅速增长,并期待用基于浏览器的应用程序代替桌面应用程序。无论是电子表格,还是薪水册和电子邮件应用程序,现在都在浏览器中再现了类似于桌面的体验。随着这些应用程序数量的增多和功能的日益复杂,JavaScript 库将会变得越来越重要,因为它是构建这些应用程序的坚实基础。JQuery 将成为开发人员最佳选择。本系列文章深入探索了 jQuery,并提供了坚实的基础。开发人员借助这个基础就可以快速轻松地构建自己的 RIA。

上一篇文章 中,您了解了 jQuery 的基础知识,包括如何在开发环境中设置 jQuery,以及它的核心函数是如何工作的。您看到了 jQuery 如何通过选择和过滤方法来简化搜索和查找页面元素,以及如何才能找到所需的元素。接下来,您还体验了 jQuery 提供的遍历搜索结果的各种方法,而且其中的很多函数与您在其他编程语言中接触到的函数十分相似。最后,还介绍了一个示例小部件,即 Select All/Deselect All 复选框,并看到了 jQuery 如何通过几行代码来构建这个小部件。

在本篇本章,通过更精彩的功能来进一步扩展您的 jQuery 知识,并向老的 Internet 应用程序添加一些真正的 “富” 特性,使这个演示应用程序成为富 Internet 应用程序。我先向您展示 jQuery 如何处理事件。所谓事件就是指鼠标单击、突出显示和拖动等。请注意,事件处理并不局限于按钮,还包括处理在任何 div、span 等上进行的鼠标单击。接下来,我将讨论如何获取和设置 Web 页面内的对象的属性。这包括如何从表单元素获得文本、如何从 div 获得 innerHTML,以及如何确定哪个类附加到哪个元素上。最后,我将讨论如何在无需重新加载页面或调整外部样式表的情况下修改页面元素的 CSS 属性。

示例应用程序在这个 Web 邮件应用程序的额外小部件中包含了各种东西,展示了如何创建一个客户端的富应用程序,并且根据与页面的交互来更改对象的颜色、大小和位置。(这些交互只限于客户端 — 下一篇文章将会添加服务器端的交互)。通过本文,您将能够掌握创建 RIA 所需的 jQuery 工具,并借此打动您的客户。

事件

jQuery 内的 Events 模块是向 Web 应用程序添加交互性的第一步,因为事件通常是页面上发生的事情的触发器。正如我在简介中提到的,您不应该认为事件只发生在 Form 元素 — 实际上,任何元素都能触发事件,因此应该充分利用这一点来更轻松地构建定制的小部件,以及添加一些独特却又不局限于特定 Form 元素的交互。

众所周知,大多数事件 基于 Form 元素。演示这些方法最好的方式就是使用它们。在开始深入研究可用的函数之前,一定要注意:Events 模块针对每个函数都遵循一种模式。每个事件函数都包含两种形式:一个没有任何参数,一个包含一个函数作为参数。二者间的差异十分重要,而且这对各个函数都是一致的。没有参数的函数将实际激发该事件。换而言之,调用 click() 将实际导致该按钮被单击。在实际单击该按钮,或其 click() 函数被调用时,将会调用 click(function)。是不是很困惑?这只是文字上的描述,举例说明之后,您就会清楚了。

清单 1. jQuery Event 方法
// make the "myButton" click.  This will cause the button to click and any actions
// tied to it will occur - for example, it could submit a form, or other
// jQuery actions could be tied to it.
$("#myButton").click();

// use jQuery to setup what will actually happen when the "myButton" is
// actually clicked.
$("#myButton").click(function(){
   $("#myDiv").toggle();
});

// A common pattern in jQuery when setting up actions on a page is to trigger the
// action to occur initially when the page is loaded.  This occurs frequently
// with AJAX setups, where the values come from the server. 
// In this example, the myDiv has its visibility toggled every button click.  When
// the page is loaded, we call click() immediately, which toggles the view
// as soon as the page views (not a practical example, but you should see the design)
$("#myButton").click(function(){
   $("#myDiv").toggle();
}).click();

如下所列的事件 符合我在之前所介绍的设计模式,而且也都具有与其相关联的两个函数。为了简单起见,我只列举了第一个:

  • blur() - 在 Form 元素失去焦点时调用,例如,用 tab 键移出具有焦点的文本字段
  • change() - 在 Form 元素失去焦点,并且其值因获得焦点而更改时调用。Internet Explorer 和 Firefox 对此的处理稍微不同。
  • click() - 当在页面元素(不一定是 Form 元素)上单击时调用
  • dblclick() - 当在页面元素(不一定是 Form 元素)上双击时调用
  • error() - 当元素出现内部错误时调用,不同的浏览器对此的处理不同,可能很多人都亲自体验过
  • focus() - 当某个 Form 元素获得焦点时调用
  • keydown() - 当页面元素在其上/内发生一个 keypress 时调用
  • keyup() - 当页面元素在其上/内释放一个 keypress 时调用
  • keypress() - 当相同的页面元素相继发生 keydown 和 keypress 时调用
  • select() - 文本在文本字段内被选中时调用,而不是内容在组合框内被选中时(这时发生的是更改事件)调用。
  • submit() - 提交 Form 时调用

还有一些函数不遵守上文概括的模式,而且也只包含一个可调用的函数。我将这些例外也列于此,但它们不常用:

  • resize(fn) - 对象调整大小时调用
  • scroll(fn) - iframe 卷起时调用
  • load(fn)/unload(fn) - 对象在页面上加载/重载时发生

此外,您可以想像得到,一些事件是与鼠标紧密相连的。由于这些事件常被误用,所以我将它们包括在第三节。认识到这一点后,jQuery 已经用特定的函数替代了其中的一些事件。我将它们列于此,只是为了直接与底层的 DOM 事件相匹配,但对于所有的实际使用,最好使用其他的方法。比如,当鼠标在某个元素上按下或释放时,就会调用 mousedown(fn)mouseup(fn) 方法。然而,更多时候,却应该调用 click() 方法,因为此方法也会作为事件抛出,而且它更符合预期的行为,更不容易出错。试想一下这样的情况:用户在某个按钮上按下鼠标,发现错了,立即松开鼠标而不是释放鼠标。如果用户用所定义的 mouseup(fn) 在另一个页面元素之上释放鼠标,那么应该发生什么行为呢?理想情况下,这两个函数的使用应该限于对界面的拖放,因为这种情况下单击不会有合适的替代。

Event 模块的最后两个方法 mouseover(fn)mouseout(fn) 目前在很多 Web 站点上都很常见。它们常被用来显示悬浮帮助、图片显示所需的相框和基于鼠标指针所在位置而发生的颜色改变。JQuery 认识到这两个函数还将很常用,但很多人都不能正确使用它们,这就导致很多错误。人们并不是有意要将 bug 引入其代码,而是自己不会编写嵌套组件或应对其他复杂情况的代码。因此,jQuery 向 Event 模块添加了一个新方法来替代这两个函数,这个方法就是 hover(fn1, fn2) 函数。

清单 2. jQuery 的 Hover 方法
// This demonstrates the hover() function as implemented by jQuery.  You define two
// functions:  what happens when the mouse moves over the specified element
// and what happens when the mouse moves off the element.
// In this example, each row in a table will get a red background when
// the mouse moves over it and a white background when the mouse leaves.
$("tr").hover(function(){
   $(this).css("background","#0000ff");
},
function(){
   $(this).css("background","#ffffff");
});

属性

页面交互性的一个体现就是它从页面的某个区域获得信息并将信息传送到其他位置的能力。这可以很具体,比如从一个文本字段获得信息后将信息放入一个表内;也可以很宽泛,比如从一个组合框获得信息后将信息传给服务器,然后将服务器的响应再放入另外一个不同的组合框内。交互性的核心就是页面信息的传递。

在页面上保存信息有很多不同的方式,在页面的某个元素内存储信息的方法就更多样了。您可能会想,一个简单的 <p> 所包含的信息肯定没有一个文本字段那么多(这不一定正确),因而,访问信息也有很多不同的函数。同样地,您自己可能已经有了这样的结论:如果能从页面元素获得信息,那么也应该可以在这些元素上放置信息。实际上,每个页面元素都是一个数据对象,其中包含由 getter/setter 方法封装的变量。JavaBean 模型和 jQuery 的实际差异是方法名称和某些元素不适合特定函数的限制。

在深入研究这些方法之前,让我们先来看看什么信息 存储到页面元素内。简单一些的,像 a <p>,可能只包含 CLASS 或 ID 信息。而像 <img> 则可能包含更多信息,比如 “src”、“alt”、“width” 和 “height”。而复杂一些的,像 < input type="password"> 则可能包含 “defaultValue”、“maxLength”、“readOnly” 或 “accessKey” 等信息。

这种潜在变量的多样性促使 jQuery 创建了一种广义函数来访问它们。这个函数是 attr(name),可用来访问来自任何页面元素的信息。我们将通过几个示例来了解其工作原理。

清单 3. jQuery attr() 函数
<img src="/images/space.gif" id="spacer" class="a b c" alt="blank">

// Calls to the attr() function will return the following
$("#spacer").attr("src"); // will return "/images/space.gif"
$("#spacer").attr("alt"); // will return "blank"

// Similarly, you can access the ID in the same way
$(img).each(function(){
   $(this).attr("id"); // will return "spacer"
});

在试图向页面添加交互性时,此函数十分有用。实际上,在添加 data() 函数(如下所示)之前,通常都必须将所需信息压缩到一个可用变量内。例如,假设有一个页面具有两个框架,第一个框架显示选项卡,底部框架显示每个选项卡的内容,那么可以这样设置:

清单 4. 应用 attr()
<!-- This would appear in the top frame as a tab.  The CSS file would control how
     the tab appears, and the only HTML code needed would be this -->
<td>
   <div class="tab" id="/messages.jsp">Messages</div>
</td>

// This code would appear in the jQuery section.  Notice how we get the ID from the tab,
// and use that information to set the bottom frame, named 'content' with the content
// on the page "messages.jsp"

$(".tab").click( function() {
   window.parent.frames['content'].location = $(this).attr("id");
});

除了获得 每个元素上的属性值外,还可以设置 这些值。其效果与以编程方式更改元素外观或行为相同。

清单 5. 利用 attr(str) 更改属性
// will change the image source, and the image displayed on the page will change
$("img").attr("src", "myimage.jpg");

// will change all the links on the page to go to one specific page
$("a").attr("href", "mypage.html");

// will change the maxLength on all password fields to 10 characters
$(":password").attr("maxLength", "10");

页面上的 Form 元素具有一个特殊的函数,该函数可以针对这些元素调用以获得附加到元素的值。在处理表单和验证时,这一点尤其便利,而且在用 Form 元素创建交互 Web 站点时更有可能会用到这些函数。

清单 6. Form 元素的 val() 函数
// will get the text contained in the text field and check that it's not blank
$(":textfield").each(function(){
   // use the val() function to get the text inside the textfield
   if ($(this).val() == "")
       $(this).next().text("Error");
});

// on a new password page, this will compare the new one with the confirmation,
// to make sure they are equal
if ($("#newPassword").val() != $("#confirmPass").val())
   $("#newPassword").next().text("Error");

还有其他一些函数,可用来获得包含在某些标记之内的信息。那么这有什么用途呢?比如说,您可以获得包含在某个 <td> 标记内的所有信息并进行替换,或者您也可以将所有 <p> 内的文本变成小写的。获得这些信息的方式有两种,但不能为此使用 attr() 函数。与所有其他的属性函数类似,这些函数也有相应的 setter 方法。第一个是 html() 函数,它能返回某个标记的所有 innerHTML。另一个是 text(),它能返回某个标记内的所有文本。那么二者有何区别呢?html() 函数能返回包括 HTML 标记在内的文本,而 text() 则会分离二者,只返回内含的文本。以下示例展示了它们的不同之处。

清单 7. html() 与 text() 的对比
// this will examine every <td> tag, and if the value is blank, it will insert
// a "-" into it, as a placeholder.
$("td").each(function(){
   // check the text of the table cell
   if ($(this).text() == "")
       $(this).text("-");
});

// this will convert every paragraph's text to lowercase
$("p").each(function(){
   var oldText = $(this).text();
   var newText = oldText.toLowerCase();
   $(this).text(newText);
});

<-- This shows the difference between text() and html() -->
<div id="sample"><b>This is the example</b></div>

$("#sample").html(); // will return "<b>This is the example</b>"
$("#sample").text(); // will return "This is an example"

此外,最近还向 jQuery 库添加了用于属性的 data() 函数。它源自 jQuery UI 项目并且已纳入 jQuery 的整体项目之中。起初,UI 项目开发人员只是觉得他们不想破坏某些页面元素的可用属性,于是就想到要找到一种方法,用来根据自己的需要创建能存储信息的属性。回顾上文提到过的选项卡的例子。我其实 “破坏” 了此 DIV 的 ID 内的链接,而这显然不是最理想的方法。但是,受 jQuery 以前版本的限制,这在当时是惟一的选择。有了 data() 函数之后,这个问题就有了更好的解决方案。不妨将 data() 函数视为用来访问包含在每个页面元素的内部 Map 的一种方式。一个 Map 实际上就是键-值对的集合。这就让开发人员可以创建他们想要给页面元素提供的任何定制属性,并能给该属性附加任意值。最终的结果就是代码的编写更简单,而且当项目规模不断增大时,代码的维护也更容易。接下来,让我们用新的 data() 函数重写上文提到的示例:

清单 8. 新的 data() 函数
// create the div like we did above, but without any specific information.  In this
// way we can create a generic HTML layout and customize it in our jQuery code.

<td>
   <div class="tab"></div>
</td>

// Now customize each tab in the jQuery code.

$(".tab").eq(0).text("Messages");
$(".tab").eq(0).data("link", "messages.jsp");
$(".tab").click(function(){
   window.parent.frames['content'].location = $(this).data("link");
});

// Taking this a step further, you can picture all this information coming from
// an external properties file via a Java array.  This would be the code on a JSP
// page.  

<%
  // array containing tab names
  String[] tabNames;
  // array containing tab links
  String[] links;

  for (int i=0; i<tabNames.length; i++) {
%>
  $(".tab").eq(<%=i%>).text("<%=tabNames[i]%>");
  $(".tab").eq(<%=i%>).data("link", "<%=links[i] %>");
<% } %>

$(".tab").click(function(){
   window.parent.frames['content'].location = $(this).data("link");
});

CSS 处理

本文最后的这个部分将要介绍如何在不调整样式表或重载此页面的情况下动态地处理页面的 CSS。我们将能够通过简单更改颜色、字体等向页面添加一些基本效果。jQuery 的 CSS 实际上是整个库最初的创意来源。其目标是让页面上的 CSS 编程更容易。正如您所见,此项目随后得到了充分的发展。但是项目的初衷并没有改变,jQuery 的确简化了 CSS 编程。不过,我还是先要说明一点,即 jQuery 为处理 CSS 所提供的那些传统函数实际上已经不能适应当今的 Web 环境。然后,我会介绍可以使用的其他函数(也是 jQuery 内的)。

有两个基本函数可用来处理页面上的 CSS。可以先以字符串传递单个属性,然后再以字符串传递单个值,也可以用字符串/字符串数组的形式一次传递。这两个函数的功能基本相同,并且可以很容易地更改页面的 CSS。

清单 9. css() 函数
// change the background of every div to red
$("div").css("backgroundColor", "#ff0000");
// - or - 
$("div").css("backgroundColor", "red");
// - or - 
$("div").css({backgroundColor: "#ff0000"}); // notice the braces and lack of quotes

可以看出这些函数非常简单直观,很容易理解和掌握。不过,考虑到目前 Web 页面设计的潮流,这些函数还存在一些问题。常规的 Web 页面都是从页面删除样式,然后在样式表中填入一个外部文件或代码片段。如果能采用其他办法,您肯定 希望将样式代码放入 JavaScript 代码中。否则,将来更改站点的外观将十分困难。

幸运的是,现在已经有了替代函数,它既能提供所需的代码分离,又能让 CSS 处理简单直观。这些函数允许从页面元素添加和删除类。通过将这些类的样式放入外部样式表,就能分离样式、数据和事件,这种分离对于复杂的页面至关重要。请看以下这些示例:

清单 10. 更佳的 CSS 处理 - addClass() 和 removeClass()
// will add the "input_error" class to any form elements that fail to validate
// you can picture the "input_error" class in the external CSS file defining
// a red border and red text

$(":textfield").each(function(){
   if ($(this).val() == "")
   {
       $(this).next().text("Error");
       // this will turn the text field's border/text red
       $(this).addClass("input_error");
   }
   // this tests if the text field has the class attached already
   else if ($(this).hasClass("input_error"))
   {
       $(this).next().text("");
       // this will remove the class, restoring a normal border/text
       $(this).removeClass("input_error");
   }
});

如这个示例所示,通过引用在外部样式表内定义的类来调整 CSS 是一种更可取的处理 CSS 的方法。它让 Web 站点创建者通过更改样式表就能改变整个站点 错误消息的处理方式,而不再像采用 css() 方法时那样,必须追究代码的每个实例。虽然这些方法直观易用,但是不太适合大型 Web 应用程序,应该避免采用 addClass()removeClass() 方法。

综合学到的知识

要综合使用以上知识,让我们再来看看这个示例应用程序。它是一个具有交互性的 Web 应用程序,试图创建一个 RIA 并给用户这样的感觉:他们处理电子邮件所用的这个 Web 应用程序非常类似于桌面应用程序。在本例中,将利用 Event、Attribute 和 CSS 模块来定义 Web 邮件应用程序将如何处理鼠标单击和双击。以下所示的屏幕截图显示了真实的效果。当用户在表的一个行上单击时,此行将会改变颜色以突出显示用户的当前选择。当用户在消息上双击时,用户就能看到消息,但如果用户读的是条新消息,此行的背景颜色会改变以表明此消息不再是未读消息。

图 1. 在某行上单击
在某行上单击
图 2. 在某行上双击
在某行上双击
清单 11. 综合学到的知识
// First we add the rows to the table.  Each row is a member of the "messageRow" class.
//  We also give an ID to each row, and this ID is the message number itself, which is 
// gotten from the Java data object. Keep in mind this sits in a for loop in a JSP file.

<%
   for (int i=0; i<messages.size(); i++)
   {
      MessageData message = messages.get(i);
%>
      <tr class="messageRow" id="<%=message.id %>">
      
// Now that the table has been laid out, we can define our jQuery code to capture single
// mouse clicks and double mouse clicks.

// Notice how we capture a single click on the table row with the click() function.  Next
// notice how we use addClass() and removeClass() instead of manipulating the CSS
// directly with a css() function.  This lets us change the stylesheet underneath
// the code without modifying our jQuery code.
$(".messageRow").click(function() {
     $(".messageRow").removeClass("message_selected");
     $(this).addClass("message_selected");
});

// Now we capture the double click on a table row.  Ignore the AJAX methods with the
// post() function, which we'll get to in the next article.
// We use the dblclick() function here to capture double clicks.
// Notice in the AJAX call, how we get the ID out of the table row that was double 
// clicked.
// We pass this ID to the server in order to get the information about the message back
// from the server.  We defined the message number in the JSP code, so that the ID
// contained the message number.
$(".messageRow").dblclick(function() {
     if ($(this).hasClass("mail_unread"))
     {
         $(this).removeClass("mail_unread");
     }
     $.post("<%=HtmlServlet.READ_MESSAGE%>.do", 
     {
            messageId: $(this).attr("id"),
            view: "<%=view %>"}, 
            function(data){
            // Do AJAX stuff here
            });
     });
});

结束语

随着应用程序不断从桌面向浏览器转移,像 jQuery 这样的 JavaScript 库的将越来越重要。应用程序会越来越复杂,这就使跨浏览器的 jQuery 成为所有 Web 应用项目的必要工具。由于易于使用和功能完备,jQuery 逐渐从其他 JavaScript 库中脱颖而出,成为很多开发人员的最佳选择。

通过这个 jQuery 系列的第二篇文章,您实际体会到了 Web 页面上的交互、了解如何在客户端实现基本交互(无需从服务器获得信息),并借此扩展了自己的 jQuery 知识。您先是接触了 Event 模块,该模块定义页面元素如何响应各种交互,其中包括鼠标交互、键盘交互和焦点交互。事件是 Web 页面上的交互的最主要的驱动器,而且它们不必附加于 Form 元素。我随后讨论了属性以及如何恰当地从页面元素获得属性,如何在页面元素上设置属性。您看到了通用的 attr() 函数可用于每个元素,并且 Form 元素有获得其值的特殊函数。您还看到了新添加到 jQuery 的 data() 函数,此函数可充当每个页面元素的 HashMap,让程序员可以创建所需的任何属性。最后,您了解到如何修改页面元素的 CSS,而不需重新加载页面。您还体验了 css() 函数的简单和直观。但是为了分离页面的样式和 jQuery 代码,您和您的团队最好选择用 addClass()removeClass() 函数替代前面那些函数。

本文的最后的一部分将您所学到的这三个模块综合起来,展示了示例邮件应用程序如何处理鼠标交互。通过突出显示单击的行以及在适当的时候对消息进行 “未读” 标记,可以区分鼠标单击和双击,然后为特定于消息的数据向服务器发送一个 Ajax 调用,将消息编号传递给服务器。

在本系列的下一篇文章中,我们将要深入研究 jQuery 内的 Ajax 函数,了解它如何消除处理 Ajax 所涉及到的大量复杂特性,而使其简单到像通常那样只需调用 JavaScript 函数。此外,我们还会谈到 jQuery 内的 Effects 模块,对于为用户创建额外的交互和视觉效果,这十分有用。下一篇文章中的最后部分将会总结整个演示邮件应用程序和我们的 jQuery 课程。到那时,希望您能自信地将这个库添加到自己的 Web 应用程序中。


下载

描述名字大小
包含示例应用程序的 Zip 文件jquery.zip68KB
包含示例应用程序的 War 文件jquery.war68KB

参考资料

学习

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=345624
ArticleTitle=使用 jQuery,第 2 部分: 构建未来的 Web 应用程序
publish-date=10102008