10.3 自定义事件

由浏览器的DOM实现自然触发的事件对任何Web应用都是至关重要的。但是,在jQuery代码中并不局限于使用这些事件。而是可以在这些事件基础上,再添加自定义事件。我们曾在第8章简单介绍过jQuery UI部件如何触发事件,但本节将讨论如何在不创建插件的情况下创建和使用自定义事件。

自定义事件必须在代码中通过手工方式来触发。从某种意义讲,自定义事件类似于我们平常定义的函数,因为它们都是一个预定义的代码块,可以在脚本中的其他地方调用执行。.on()方法对应着一个函数的定义,而.trigger()方法对应着一次函数调用。

但事件处理程序与触发它们的代码是分离的。这意味着我们可以在任何时间触发事件,而不需要知道触发事件之后会发生什么。常规的函数调用只能执行一段代码,而自定义事件可以触发执行一个绑定的事件处理程序,也可以触发执行多个事件处理程序,甚至可以不执行任何事件处理程序。

为了演示前面描述的这些内容,可以修改Ajax加载功能,从而使用一个自定义函数。在用户请求更多照片时,我们会触发一个nextPage事件,同时为这个事件绑定相应的处理程序,而在.click()处理程序中完成之前所做的工作,参见代码清单10-9。

代码清单10-9

  1. $(document).ready(function() {
  2. $('#more-photos').click(function(event) {
  3. event.preventDefault();
  4. $(this).trigger('nextPage');
  5. });
  6. });

好,现在的 .click() 处理程序的工作只剩下很少了。在触发自定义事件后,通过调用.preventDefault(),它又阻止了默认的行为。大部分工作量都转移到了针对nextPage事件的新的事件处理程序中了,参见代码清单10-10。

代码清单10-10

  1. (function($) {
  2. $(document).on('nextPage', function() {
  3. var url = $('#more-photos').attr('href');
  4. if (url) {
  5. $.get(url, function(data) {
  6. $('#gallery').append(data);
  7. });
  8. }
  9. });
  10.  
  11. var pageNum = 1;
  12. $(document).on('nextPage', function() {
  13. pageNum++;
  14. if (pageNum < 20) {
  15. $('#more-photos').attr('href', 'pages/' + pageNum + '.html');
  16. }
  17. else {
  18. $('#more-photos').remove();
  19. }
  20. });
  21. })(jQuery);

其中大部分代码与代码清单10-2相同。最大的区别是把原来的一个函数拆成了两个。这样做的目的只是为了演示一次触发可以导致多个绑定的处理程序运行。单击More Photos链接会导致追加下一组照片,同时更新链接的href属性,如图10-3所示。

图 10-3值得注意的是,我们还在这个例子中展示了事件冒泡的另一种应用。如果把nextPage处理程序绑定到触发该事件链接上,那就需要等到DOM就绪。于是,我们在这里把处理程序绑定到了文档自身,因为页面只要一打开文档就立即可用,所以可以在$(document).ready()外部来完成绑定。实际上,我们在代码清单10-8中也运用了相同的原理,当时是把.on()方法的绑定转移到了$(document).ready()外部。而利用事件冒泡,只要另一个处理程序不阻止事件传播,我们的处理程序可以被触发。

10.3 自定义事件 - 图1

图 10-3

10.3.1 无穷滚动

就像多个不同的事件处理程序可以响应相同事件一样,相同的事件也可以通过多种不同的方式来触发。为了演示这一点,我们接下来会给页面添加一个无穷滚动功能。所谓无穷滚动,是一种让用户控制滚动条来加载内容的流行技术,即当到达目前加载的内容底部时,就会自动取得新内容。

我们先从一个简单实现开始,然后在后续的例子中逐步改进它。这个例子的基本思想就是监听scroll(滚动)事件,在发生滚动事件时测量当前滚动条的位置,必要时就加载新的内容。下列代码会触发代码清单10-10中定义的nextPage事件。

代码清单10-11

  1. (function($) {
  2. function checkScrollPosition() {
  3. var distance = $(window).scrollTop() + $(window).height();
  4. if ($('#container').height() <= distance) {
  5. $(document).trigger('nextPage');
  6. }
  7. }
  8. $(document).ready(function() {
  9. $(window).scroll(checkScrollPosition).trigger('scroll');
  10. });
  11. })(jQuery);

这个新的checkScrollPosition()函数在这里作为windowscroll事件处理程序。它会计算从文档的顶部到窗口底部的距离,然后用这个距离与文档中主容器的高度进行比较。只要这两个值一相等,就需要使用额外的照片来填充页面,因此就触发nextPage事件。

接下来绑定scroll处理程序,并通过调用.scroll()方法立即触发它。这样就开始了整个过程,如果此时页面中还没有照片,就会发出一个Ajax请求,如图10-4所示。

10.3 自定义事件 - 图2

图 10-4

10.3.2 自定义事件参数

在定义函数时,可以设置任意数量的参数。而在调用函数时,再给这些参数实际地传入值。类似地,在触发自定义事件时,我们也可以给任何注册的事件处理程序传入额外的信息。这种技术就叫做自定义事件参数

任何事件处理程序的第一个参数是由jQuery增强和扩展之后的DOM事件对象。在这个参数之后,我们可以根据需要传递任意数量的参数。

下面我们就来实际地看一看自定义事件参数。为此,可以给代码清单10-10中的nextPage事件增加一个选项,通过它来指定是否向下滚动以显示新添加的内容,参见代码清单10-12。

代码清单10-12

  1. (function($) {
  2. $(document).on('nextPage', function(event, scrollToVisible) {
  3. var url = $('#more-photos').attr('href');
  4. if (url) {
  5. $.get(url, function(data) {
  6. var $data = $(data).appendTo('#gallery');
  7. if (scrollToVisible) {
  8. var newTop = $data.offset().top;
  9. $(window).scrollTop(newTop);
  10. }
  11. checkScrollPosition();
  12. });
  13. }
  14. });
  15. });

我们已经给事件回调函数添加了scrollToVisible参数。这个参数的值指定了是否执行新的功能,即测量新内容的位置并滚动到该位置。测量只要使用.offset()方法即可,这个方法返回新内容的topleft坐标。要向下滚动页面,调用.scrollTop()方法。

接下来就需要向这个新参数传入一个实际的值。换句话说,就是在使用.trigger()方法触发事件时多提供一个值。在通过页面滚动触发nextPage事件时,我们不想让这个新的行为发生,因为用户已经在直接操作滚动位置了。而在用户单击More Photos时,我们希望新添加的内容显示在屏幕上,因而就要像代码清单10-13这样给处理程序传递一个true值。

代码清单10-13

  1. $(document).ready(function() {
  2. $('#more-photos').click(function() {
  3. $(this).trigger('nextPage', [true]);
  4. return false;
  5. });
  6.  
  7. $(window).scroll(checkScrollPosition).trigger('scroll');
  8. });

在调用.trigger()方法时,我们额外给事件处理程序传递了一个数组。而这个值为true的数组在代码清单10-11中会把true赋值给参数scrollToVisible

这个自定义事件参数在调用和接收的任何一端都是可选的。在代码对.trigger()的两次调用中,只有一次提供了这个参数值。另一次调用也不会导致错误,因为未传递的参数将以undefined值代替。类似地,在调用.on('nextPage')时不传递scrollToVisible参数也不会出错。因为在传递实际的值而参数却不存在时,传递过来的值就会被忽略。