3.2 处理简单的事件
除了页面加载之外,我们也想在其他时刻完成某个任务。正如JavaScript可以让我们通过<body onload="">
或window.onload
来截获页面加载事件一样,它对用户发起的事件也提供了相似的“挂钩”(hook)。例如,鼠标单击(onclick
)、表单被修改(onchange
)以及窗口大小变化(onresize
)等。在这些情况下,如果直接在DOM中为元素指定行为,那么这些挂钩也会与我们讨论的onload
一样具有类似的缺点。为此,jQuery也为处理这些事件提供了一种改进的方式。
3.2.1 简单的样式转换器
为了说明某些事件处理技术,我们假设希望某个页面能够基于用户的输入呈现出不同的样式。也就是说,允许用户通过单击按钮来切换视图,包括正常视图、将文本限制在窄列中的视图和适合打印的大字内容区视图。
渐进增强
在创建样式转换器时,优秀的Web开发人员应该遵守渐进增强的原则。第5章还会学习怎么在jQuery代码中向样式转换器内注入内容,让禁用JavaScript的用户看不到与功能无关的控件。
用于样式转换器的HTML标记如下所示:
<div id="switcher" class="switcher">
<h3>Style Switcher</h3>
<button id="switcher-default">
Default
</button>
<button id="switcher-narrow">
Narrow Column
</button>
<button id="switcher-large">
Large Print
</button>
</div>
下载代码示例
如同本书其他HTML、CSS以及JavaScript示例一样,上面的标记只是完整文档的一个片段。如果读者想试一试这些示例,可以从以下地址下载完整的示例代码:Packt Publishing 网站 http://www.packtpub.com/support ,或者本书网站 http://book.learningjquery.com/。
在与页面中其他HTML标记和基本的CSS组合以后,我们可以看到如图3-1所示的页面外观。
图 3-1
首先,我们来编写Large Print按钮的功能。此时,需要一点CSS代码来实现页面的替换视图:
body.large .chapter {
font-size: 1.5em;
}
然后,我们的目标就是为<body>
标签应用large
类。这样会导致样式表对页面进行重新格式化。按照第2章介绍的知识,添加类的语句如下所示:
$('body').addClass('large');
但是,我们希望这条语句在用户单击按钮时执行(而不是像我们到目前为止看到的那样在页面加载后执行)。为此,我们需要引入.on()
方法。通过这个方法,可以指定任何DOM事件,并为该事件添加一种行为。此时,事件是click
,而行为则是由上面的一行代码构成的函数,参见代码清单3-1。
代码清单3-1
$(document).ready(function() {
$('#switcher-large').on('click', function() {
$('body').addClass('large');
});
});
现在,当单击Large Print按钮时,就会运行函数中的代码,而页面的外观将如图3-2所示。
图 3-2
这里的全部操作就是绑定了一个事件。我们前面介绍的.ready()
方法的优点在此也同样适用。多次调用.on()
也没有任何问题,即可以按需为同一个事件追加更多的行为。
但是,这还不是完成上述任务的最优雅或者说最有效的方式。随着本章内容的展开,我们会对刚才的代码加以扩展和改进,使其达到足以令我们自豪的水平。
3.2.2 启用其他按钮
现在,Large Print按钮开始生效了。接下来,我们要以类似的方式处理其他两个按钮(Default和Narrow),让它们也都执行各自的任务。这个过程很简单,即分别使用.on()
为它们添加一个单击处理程序,同时视情况移除或添加类。完成之后的代码如代码清单3-2所示。
代码清单3-2
$(document).ready(function() {
$('#switcher-default').on('click', function() {
$('body').removeClass('narrow');
$('body').removeClass('large');
});
$('#switcher-narrow').on('click', function() {
$('body').addClass('narrow');
$('body').removeClass('large');
});
$('#switcher-large').on('click', function() {
$('body').removeClass('narrow');
$('body').addClass('large');
});
});
以下是配套的narrow
类的CSS规则:
body.narrow .chapter {
width: 250px;
}
现在,如果单击Narrow Column按钮,随着相应的CSS生效,文本会相应变化,如图3-3所示。
图 3-3
单击Default按钮将从<body>
标签中同时移除两个类,让页面恢复为初始状态。
3.2.3 利用事件处理程序的上下文
虽然样式转换器的功能很正常,但我们并没有就哪个按钮处于当前使用状态对用户给出反馈。为此,我们的方法是在按钮被单击时,为它应用selected
类,同时从其他按钮上移除这个类。selected
类只是为按钮文本添加了粗体样式:
.selected {
font-weight: bold;
}
为了实现类的变换,可以按照前面的做法,通过ID来引用每个按钮,然后再视情况为它们应用或移除类。不过,这一次我们要探索一种更优雅也更具扩展性的解决方案,这个方案利用了事件处理程序运行的上下文。
当触发任何事件处理程序时,关键字this
引用的都是携带相应行为的DOM元素。前面我们谈到过,$()
函数可以将DOM元素作为参数,而this
关键字是实现这个功能的关键1。通过在事件处理程序中使用$(this)
,可以为相应的元素创建jQuery对象,然后就如同使用CSS选择符找到该元素一样对它进行操作。
1 即允许向$()
函数传递DOM元素,也是为了更方便地将引用DOM元素的this
转换为jQuery对象。
知道了这些之后,我们可以编写出下面的代码:
$(this).addClass('selected');
把这行代码放到那3个事件处理程序中,就可以在按钮被单击时为按钮添加selected
类。要从其他按钮中移除这个类,可以利用jQuery的隐式迭代特性,并编写如下代码:
$('#switcher button').removeClass('selected');
这行代码会移除样式转换器中每个按钮的selected
类。
我们还应该在文档就绪时把这个类添加到Default按钮上。因此,按照正确的次序放置它们,
就可以得到代码清单3-3。
代码清单3-3
$(document).ready(function() {
$('#switcher-default')
.addClass('selected')
.on('click', function() {
$('body').removeClass('narrow');
$('body').removeClass('large');
$('#switcher button').removeClass('selected');
$(this).addClass('selected');
});
$('#switcher-narrow').on('click', function() {
$('body').addClass('narrow');
$('body').removeClass('large');
$('#switcher button').removeClass('selected');
$(this).addClass('selected');
});
$('#switcher-large').on('click', function() {
$('body').removeClass('narrow');
$('body').addClass('large');
$('#switcher button').removeClass('selected');
$(this).addClass('selected');
});
});
这样,样式转换器就会对用户给出适当的反馈了。
利用处理程序的上下文将语句通用化,可以使代码更高效。我们可以把负责突出显示的代码提取到一个单独的处理程序中,因为针对3个按钮的突出显示代码都一样,结果如代码清单3-4所示。
代码清单3-4
$(document).ready(function() {
$('#switcher-default')
.addClass('selected')
.on('click', function() {
$('body').removeClass('narrow').removeClass('large');
});
$('#switcher-narrow').on('click', function() {
$('body').addClass('narrow').removeClass('large');
});
$('#switcher-large').on('click', function() {
$('body').removeClass('narrow').addClass('large');
});
$('#switcher button').on('click', function() {
$('#switcher button').removeClass('selected');
$(this).addClass('selected');
});
});
这一步优化利用了我们讨论过的3种jQuery特性。第一,在通过对.on()
的一次调用为每个按钮都绑定相同的单击事件处理程序时,隐式迭代机制再次发挥了作用。第二,行为队列机制让我们在同一个单击事件上绑定了两个函数,而且第二个函数不会覆盖第一个函数。最后,我们使用jQuery的连缀能力将每次添加和移除类的操作压缩到了一行代码中。
3.2.4 使用事件上下文进一步减少代码
我们刚才的代码优化实际上是在做重构——修改已有代码,以更加高效和简洁的方式实现相同任务。为寻找进一步重构的机会,下面再看一看绑定到每个按钮的行为。其中,.removeClass()
方法的参数是可选的,即当省略参数时,该方法会移除元素中所有的类。利用这一点,可以把代码再改进得更简单一些,参见代码清单3-5。
代码清单3-5
- //改善代码
- $(document).ready(function() {
- $('#switcher-default')
- .addClass('selected')
- .on('click', function() {
- $('body').removeClass();
- });
- $('#switcher-narrow').on('click', function() {
- $('body').removeClass().addClass('narrow');
- });
- $('#switcher-large').on('click', function() {
- $('body').removeClass().addClass('large');
- });
- $('#switcher button').on('click', function() {
- $('#switcher button').removeClass('selected');
- $(this).addClass('selected');
- });
- });
注意,为了适应更通用的移除类的操作,我们对操作顺序作了小小的调整——先执行.removeClass()
,以便它不会撤销几乎同时执行的.addClass()
。
我们在这里能够完全移除所有类,是因为现在的HTML是由我们控制的。为了重用而编写代码时(例如编写插件),必须考虑到已经存在的所有类并保证它们原封不动。
此时,在每个按钮的处理程序中仍然会执行某些相同的代码。这些代码也可以轻而易举地提取到通用的按钮单击处理程序中,如代码清单3-6所示。
代码清单3-6
- $(document).ready(function() {
- $('#switcher-default').addClass('selected');
- $('#switcher button').on('click', function() {
- $('body').removeClass();
- $('#switcher button').removeClass('selected');
- $(this).addClass('selected');
- });
- $('#switcher-narrow').on('click', function() {
- $('body').addClass('narrow');
- });
- $('#switcher-large').on('click', function() {
- $('body').addClass('large');
- });
- });
这里要注意的是,必须把通用的处理程序转移到特殊的处理程序上方,因为.removeClass()
需要先于.addClass()
执行。而之所以能够做到这一点,是因为jQuery总是按照我们注册的顺序来触发事件处理程序。
最后,可以通过再次利用事件的执行上下文来完全消除特殊的处理程序。因为上下文关键字this
引用的是DOM元素,而不是jQuery对象,所以可以使用原生的DOM属性来确定被单击元素的ID。因而,就可以对所有按钮都绑定相同的处理程序,然后在这个处理程序内部针对按钮执行不同的操作,参见代码清单3-7。
代码清单3-7
$(document).ready(function() {
$('#switcher-default').addClass('selected');
$('#switcher button').on('click', function() {
var bodyClass = this.id.split('-')[1];
$('body').removeClass().addClass(bodyClass);
$('#switcher button').removeClass('selected');
$(this).addClass('selected');
});
});
根据单击的按钮不同,bodyClass
变量的值可能是default
、narrow
或large
。这里与前面做法的不同之处在于,我们会在用户单击<button id="switcher-default">
时给<body>
添加default
类。虽然在这儿添加这个类也用不着,但与因此降低的复杂性相比,仅仅添加一个用不上的类名还是很划算的。
3.2.5 简写的事件
鉴于为某个事件(例如简单的单击事件)绑定处理程序极为常用,jQuery提供了一种简化事件操作的方式——简写事件方法,简写事件方法的原理与对应的.on()
调用相同,可以减少一定的代码输入量。
例如,不使用.on()
而使用.click()
可以将前面的样式转换器程序重写为如代码清单3-8所示。
代码清单3-8
$(document).ready(function() {
$('#switcher-default').addClass('selected');
$('#switcher button').click(function() {
var bodyClass = this.id.split('-')[1];
$('body').removeClass().addClass(bodyClass);
$('#switcher button').removeClass('selected');
$(this).addClass('selected');
});
});
其他blur
、keydown
和scroll
等标准的DOM事件,也存在类似前面这样的简写事件。这些简写的事件方法能够把一个事件处理程序绑定到同名事件上面。
3.2.6 显示和隐藏高级特性
假设我们想在不需要时隐藏样式转换器。隐藏高级特性2的一种便捷方式,就是使它们可以折叠。因此,我们要实现的效果是在标签3上单击能够隐藏所有按钮,最后只剩一个标签;而再次单击标签则会恢复这些按钮。为了隐藏按钮,我们还需要另外一个类:
.hidden {
display: none;
}
2 这里所谓的高级特性就是指为页面提供样式切换能力的样式转换器。
3 这里将#switcher h3
选择的h3
标题元素称为标签。下同。
为实现这个功能,可以把当前的按钮状态保存在一个变量中,每当标签被单击时,通过检查这个变量的值就能知道应该向这些按钮中添加,还是要从这些按钮中移除.hidden
类。
不过,jQuery也为我们提供了一个简便的toggleClass()
方法,能够根据相应的类是否存在而添加或删除类,参见代码清单3-9。
代码清单3-9
$(document).ready(function() {
$('#switcher h3').click(function() {
$('#switcher button').toggleClass('hidden');
});
});
在第一次单击后,所有按钮都会隐藏起来,如图3-4所示。
图 3-4
而第二次单击则又恢复了它们的可见性,如图3-5所示。
图 3-5
同样,这里我们依靠的仍然是jQuery的隐式迭代能力,即一次就能隐藏所有按钮,而不需要使用包装元素4。
4 即不需要在这3个按钮外部再添加额外的标签(如<div>
)。如果没有隐式迭代机制,那么想一次隐藏3个按钮,一种常见的方法就是隐藏包含这3个按钮的包装元素。