3.2 处理简单的事件

除了页面加载之外,我们也想在其他时刻完成某个任务。正如JavaScript可以让我们通过<body onload="">window.onload来截获页面加载事件一样,它对用户发起的事件也提供了相似的“挂钩”(hook)。例如,鼠标单击(onclick)、表单被修改(onchange)以及窗口大小变化(onresize)等。在这些情况下,如果直接在DOM中为元素指定行为,那么这些挂钩也会与我们讨论的onload一样具有类似的缺点。为此,jQuery也为处理这些事件提供了一种改进的方式。

3.2.1 简单的样式转换器

为了说明某些事件处理技术,我们假设希望某个页面能够基于用户的输入呈现出不同的样式。也就是说,允许用户通过单击按钮来切换视图,包括正常视图、将文本限制在窄列中的视图和适合打印的大字内容区视图。

 渐进增强

在创建样式转换器时,优秀的Web开发人员应该遵守渐进增强的原则。第5章还会学习怎么在jQuery代码中向样式转换器内注入内容,让禁用JavaScript的用户看不到与功能无关的控件。

用于样式转换器的HTML标记如下所示:

  1. <div id="switcher" class="switcher">
  2. <h3>Style Switcher</h3>
  3. <button id="switcher-default">
  4. Default
  5. </button>
  6. <button id="switcher-narrow">
  7. Narrow Column
  8. </button>
  9. <button id="switcher-large">
  10. Large Print
  11. </button>
  12. </div>

 下载代码示例

如同本书其他HTML、CSS以及JavaScript示例一样,上面的标记只是完整文档的一个片段。如果读者想试一试这些示例,可以从以下地址下载完整的示例代码:Packt Publishing 网站 http://www.packtpub.com/support ,或者本书网站 http://book.learningjquery.com/

在与页面中其他HTML标记和基本的CSS组合以后,我们可以看到如图3-1所示的页面外观。

3.2.1 简单的样式转换器 - 图1

图 3-1

首先,我们来编写Large Print按钮的功能。此时,需要一点CSS代码来实现页面的替换视图:

  1. body.large .chapter {
  2. font-size: 1.5em;
  3. }

然后,我们的目标就是为<body>标签应用large类。这样会导致样式表对页面进行重新格式化。按照第2章介绍的知识,添加类的语句如下所示:

  1. $('body').addClass('large');

但是,我们希望这条语句在用户单击按钮时执行(而不是像我们到目前为止看到的那样在页面加载后执行)。为此,我们需要引入.on()方法。通过这个方法,可以指定任何DOM事件,并为该事件添加一种行为。此时,事件是click,而行为则是由上面的一行代码构成的函数,参见代码清单3-1。

代码清单3-1

  1. $(document).ready(function() {
  2. $('#switcher-large').on('click', function() {
  3. $('body').addClass('large');
  4. });
  5. });

现在,当单击Large Print按钮时,就会运行函数中的代码,而页面的外观将如图3-2所示。

3.2.1 简单的样式转换器 - 图2

图 3-2

这里的全部操作就是绑定了一个事件。我们前面介绍的.ready()方法的优点在此也同样适用。多次调用.on()也没有任何问题,即可以按需为同一个事件追加更多的行为。

但是,这还不是完成上述任务的最优雅或者说最有效的方式。随着本章内容的展开,我们会对刚才的代码加以扩展和改进,使其达到足以令我们自豪的水平。

3.2.2 启用其他按钮

现在,Large Print按钮开始生效了。接下来,我们要以类似的方式处理其他两个按钮(Default和Narrow),让它们也都执行各自的任务。这个过程很简单,即分别使用.on()为它们添加一个单击处理程序,同时视情况移除或添加类。完成之后的代码如代码清单3-2所示。

代码清单3-2

  1. $(document).ready(function() {
  2. $('#switcher-default').on('click', function() {
  3. $('body').removeClass('narrow');
  4. $('body').removeClass('large');
  5. });
  6. $('#switcher-narrow').on('click', function() {
  7. $('body').addClass('narrow');
  8. $('body').removeClass('large');
  9. });
  10. $('#switcher-large').on('click', function() {
  11. $('body').removeClass('narrow');
  12. $('body').addClass('large');
  13. });
  14. });

以下是配套的narrow类的CSS规则:

  1. body.narrow .chapter {
  2. width: 250px;
  3. }

现在,如果单击Narrow Column按钮,随着相应的CSS生效,文本会相应变化,如图3-3所示。

3.2.1 简单的样式转换器 - 图3

图 3-3

单击Default按钮将从<body>标签中同时移除两个类,让页面恢复为初始状态。

3.2.3 利用事件处理程序的上下文

虽然样式转换器的功能很正常,但我们并没有就哪个按钮处于当前使用状态对用户给出反馈。为此,我们的方法是在按钮被单击时,为它应用selected类,同时从其他按钮上移除这个类。selected类只是为按钮文本添加了粗体样式:

  1. .selected {
  2. font-weight: bold;
  3. }

为了实现类的变换,可以按照前面的做法,通过ID来引用每个按钮,然后再视情况为它们应用或移除类。不过,这一次我们要探索一种更优雅也更具扩展性的解决方案,这个方案利用了事件处理程序运行的上下文

当触发任何事件处理程序时,关键字this引用的都是携带相应行为的DOM元素。前面我们谈到过,$()函数可以将DOM元素作为参数,而this关键字是实现这个功能的关键1。通过在事件处理程序中使用$(this),可以为相应的元素创建jQuery对象,然后就如同使用CSS选择符找到该元素一样对它进行操作。

1 即允许向$()函数传递DOM元素,也是为了更方便地将引用DOM元素的this转换为jQuery对象。

知道了这些之后,我们可以编写出下面的代码:

  1. $(this).addClass('selected');

把这行代码放到那3个事件处理程序中,就可以在按钮被单击时为按钮添加selected类。要从其他按钮中移除这个类,可以利用jQuery的隐式迭代特性,并编写如下代码:

  1. $('#switcher button').removeClass('selected');

这行代码会移除样式转换器中每个按钮的selected类。

我们还应该在文档就绪时把这个类添加到Default按钮上。因此,按照正确的次序放置它们,

就可以得到代码清单3-3。

代码清单3-3

  1. $(document).ready(function() {
  2. $('#switcher-default')
  3. .addClass('selected')
  4. .on('click', function() {
  5. $('body').removeClass('narrow');
  6. $('body').removeClass('large');
  7. $('#switcher button').removeClass('selected');
  8. $(this).addClass('selected');
  9. });
  10. $('#switcher-narrow').on('click', function() {
  11. $('body').addClass('narrow');
  12. $('body').removeClass('large');
  13. $('#switcher button').removeClass('selected');
  14. $(this).addClass('selected');
  15. });
  16. $('#switcher-large').on('click', function() {
  17. $('body').removeClass('narrow');
  18. $('body').addClass('large');
  19. $('#switcher button').removeClass('selected');
  20. $(this).addClass('selected');
  21. });
  22. });

这样,样式转换器就会对用户给出适当的反馈了。

利用处理程序的上下文将语句通用化,可以使代码更高效。我们可以把负责突出显示的代码提取到一个单独的处理程序中,因为针对3个按钮的突出显示代码都一样,结果如代码清单3-4所示。

代码清单3-4

  1. $(document).ready(function() {
  2. $('#switcher-default')
  3. .addClass('selected')
  4. .on('click', function() {
  5. $('body').removeClass('narrow').removeClass('large');
  6. });
  7. $('#switcher-narrow').on('click', function() {
  8. $('body').addClass('narrow').removeClass('large');
  9. });
  10. $('#switcher-large').on('click', function() {
  11. $('body').removeClass('narrow').addClass('large');
  12. });
  13. $('#switcher button').on('click', function() {
  14. $('#switcher button').removeClass('selected');
  15. $(this).addClass('selected');
  16. });
  17. });

这一步优化利用了我们讨论过的3种jQuery特性。第一,在通过对.on()的一次调用为每个按钮都绑定相同的单击事件处理程序时,隐式迭代机制再次发挥了作用。第二,行为队列机制让我们在同一个单击事件上绑定了两个函数,而且第二个函数不会覆盖第一个函数。最后,我们使用jQuery的连缀能力将每次添加和移除类的操作压缩到了一行代码中。

3.2.4 使用事件上下文进一步减少代码

我们刚才的代码优化实际上是在做重构——修改已有代码,以更加高效和简洁的方式实现相同任务。为寻找进一步重构的机会,下面再看一看绑定到每个按钮的行为。其中,.removeClass()方法的参数是可选的,即当省略参数时,该方法会移除元素中所有的类。利用这一点,可以把代码再改进得更简单一些,参见代码清单3-5。

代码清单3-5

  1. //改善代码
  2. $(document).ready(function() {
  3. $('#switcher-default')
  4. .addClass('selected')
  5. .on('click', function() {
  6. $('body').removeClass();
  7. });
  8. $('#switcher-narrow').on('click', function() {
  9. $('body').removeClass().addClass('narrow');
  10. });
  11. $('#switcher-large').on('click', function() {
  12. $('body').removeClass().addClass('large');
  13. });
  14. $('#switcher button').on('click', function() {
  15. $('#switcher button').removeClass('selected');
  16. $(this).addClass('selected');
  17. });
  18. });

注意,为了适应更通用的移除类的操作,我们对操作顺序作了小小的调整——先执行.removeClass(),以便它不会撤销几乎同时执行的.addClass()

 我们在这里能够完全移除所有类,是因为现在的HTML是由我们控制的。为了重用而编写代码时(例如编写插件),必须考虑到已经存在的所有类并保证它们原封不动。

此时,在每个按钮的处理程序中仍然会执行某些相同的代码。这些代码也可以轻而易举地提取到通用的按钮单击处理程序中,如代码清单3-6所示。

代码清单3-6

  1. $(document).ready(function() {
  2. $('#switcher-default').addClass('selected');
  3. $('#switcher button').on('click', function() {
  4. $('body').removeClass();
  5. $('#switcher button').removeClass('selected');
  6. $(this).addClass('selected');
  7. });
  8. $('#switcher-narrow').on('click', function() {
  9. $('body').addClass('narrow');
  10. });
  11. $('#switcher-large').on('click', function() {
  12. $('body').addClass('large');
  13. });
  14. });

这里要注意的是,必须把通用的处理程序转移到特殊的处理程序上方,因为.removeClass()需要先于.addClass()执行。而之所以能够做到这一点,是因为jQuery总是按照我们注册的顺序来触发事件处理程序。

最后,可以通过再次利用事件的执行上下文来完全消除特殊的处理程序。因为上下文关键字this引用的是DOM元素,而不是jQuery对象,所以可以使用原生的DOM属性来确定被单击元素的ID。因而,就可以对所有按钮都绑定相同的处理程序,然后在这个处理程序内部针对按钮执行不同的操作,参见代码清单3-7。

代码清单3-7

  1. $(document).ready(function() {
  2. $('#switcher-default').addClass('selected');
  3. $('#switcher button').on('click', function() {
  4. var bodyClass = this.id.split('-')[1];
  5. $('body').removeClass().addClass(bodyClass);
  6. $('#switcher button').removeClass('selected');
  7. $(this).addClass('selected');
  8. });
  9. });

根据单击的按钮不同,bodyClass变量的值可能是defaultnarrowlarge。这里与前面做法的不同之处在于,我们会在用户单击<button id="switcher-default">时给<body>添加default类。虽然在这儿添加这个类也用不着,但与因此降低的复杂性相比,仅仅添加一个用不上的类名还是很划算的。

3.2.5 简写的事件

鉴于为某个事件(例如简单的单击事件)绑定处理程序极为常用,jQuery提供了一种简化事件操作的方式——简写事件方法,简写事件方法的原理与对应的.on()调用相同,可以减少一定的代码输入量。

例如,不使用.on()而使用.click()可以将前面的样式转换器程序重写为如代码清单3-8所示。

代码清单3-8

  1. $(document).ready(function() {
  2. $('#switcher-default').addClass('selected');
  3. $('#switcher button').click(function() {
  4. var bodyClass = this.id.split('-')[1];
  5. $('body').removeClass().addClass(bodyClass);
  6. $('#switcher button').removeClass('selected');
  7. $(this).addClass('selected');
  8. });
  9. });

其他blurkeydownscroll等标准的DOM事件,也存在类似前面这样的简写事件。这些简写的事件方法能够把一个事件处理程序绑定到同名事件上面。

3.2.6 显示和隐藏高级特性

假设我们想在不需要时隐藏样式转换器。隐藏高级特性2的一种便捷方式,就是使它们可以折叠。因此,我们要实现的效果是在标签3上单击能够隐藏所有按钮,最后只剩一个标签;而再次单击标签则会恢复这些按钮。为了隐藏按钮,我们还需要另外一个类:

  1. .hidden {
  2. display: none;
  3. }

2 这里所谓的高级特性就是指为页面提供样式切换能力的样式转换器。

3 这里将#switcher h3选择的h3标题元素称为标签。下同。

为实现这个功能,可以把当前的按钮状态保存在一个变量中,每当标签被单击时,通过检查这个变量的值就能知道应该向这些按钮中添加,还是要从这些按钮中移除.hidden类。

不过,jQuery也为我们提供了一个简便的toggleClass()方法,能够根据相应的类是否存在而添加或删除类,参见代码清单3-9。

代码清单3-9

  1. $(document).ready(function() {
  2. $('#switcher h3').click(function() {
  3. $('#switcher button').toggleClass('hidden');
  4. });
  5. });

在第一次单击后,所有按钮都会隐藏起来,如图3-4所示。

3.2.1 简单的样式转换器 - 图4

图 3-4

而第二次单击则又恢复了它们的可见性,如图3-5所示。

3.2.1 简单的样式转换器 - 图5

图 3-5

同样,这里我们依靠的仍然是jQuery的隐式迭代能力,即一次就能隐藏所有按钮,而不需要使用包装元素4

4 即不需要在这3个按钮外部再添加额外的标签(如<div>)。如果没有隐式迭代机制,那么想一次隐藏3个按钮,一种常见的方法就是隐藏包含这3个按钮的包装元素。