With contributions by James Padolsey, Paul Irish, and others. See the GitHub repository for a complete history of contributions.中文版由灰狐翻译团队完成:Allen、Hxgdzyuyi、Jack Hsing、Julian Wong、Leegorous、黎展波、Sky hx、Zengzhan(按字母顺序)

Licensed by Rebecca Murphey under the Creative Commons Attribution-Share Alike 3.0 United States license. You are free to copy, distribute, transmit, and remix this work, provided you attribute the work to Rebecca Murphey as the original author and reference the GitHub repository for the work. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same, similar or a compatible license. Any of the above conditions can be waived if you get permission from the copyright holder. For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to the license.


Table of Contents

1. 欢迎
获得代码
软件
在你的页面里添加JavaScript
JavaScript的调试
练习
本书所使用的一些约定
相关阅读材料
I. JavaScript 101
2. JavaScript 基础
概述
基础语法
运算符
基本运算符
对数字和字符串进行运算
逻辑运算符
比较运算符
条件语句
真与假
三元运算符的赋值
Switch分支语句
循环语句
For循环
while 循环
do-while 循环
暂停和继续
关键字
数组
对象
函数
使用函数
自执行匿名函数
函数作为参数
测试类型
作用域
闭包
II. jQuery: 基础概念
3. jQuery 基础
$(document).ready()
选择元素
做包含任何元素的选择器?
保留的选择器
改善 & 过滤选择器
选择Form元素
使用选择器
链接
Getters & Setters
CSS, Styling, & Dimensions
为样式使用CSS类
尺寸
属性
遍历
操作元素
获取和设置元素信息
移动,拷贝和删除元素
创建新元素
操作属性
练习
Selecting
Traversing
Manipulating
4. jQuery核心
$ vs $()
实用方法
校验类型
数据方法
特色 & 浏览器探测
避免与其它库的冲突
5. 事件
概述
为元素绑定事件Connecting Events to Elements
绑定事件到只执行一次
取消事件绑定
命名空间事件
在事件处理函数中的函数 Inside the Event Handling Function
触发事件处理函数
使用事件委托提高性能
解除委托事件绑定
事件助手
$.fn.hover
$.fn.toggle
练习
Create an Input Hint
Add Tabbed Navigation
6. Effects 特效
概述
基本特效
改变基本效果持续时间
特效完成后做一些处理
使用$.fn.animate自定义效果
Easing
管理效果
练习
Reveal Hidden Text
Create Dropdown Menus
Create a Slideshow
7. Ajax
Overview
Key Concepts
GET vs. Post
Data Types
A is for Asynchronous
Same-Origin Policy and JSONP
Ajax and Firebug
jQuery's Ajax-Related Methods
$.ajax
Convenience Methods
$.fn.load
Ajax and Forms
Working with JSONP
Ajax Events
Exercises
Load External Content
Load Content Using JSON
8. 插件
准确来说,什么是一个插件?
如何创建一个基本的插件
寻觅插件
自己动手写插件
使用 jQuery UI Widget Factory 编写有状态的插件(Stateful Plugins)
给 widget 添加方法
Widget 的参数处理
添加回调功能
清扫处理
结论
练习
Make a Table Sortable
Write a Table-Striping Plugin
III. 高级主题
工作进度
9. 性能调优的最佳实践
在循环中缓存 length
在循环外使用 append
保持 DRY
当心匿名函数
优化选择器
ID 选择器
具体指定
避免使用无定向选择器
使用事件委派
先将元素 detach 出来再操作
应该使用样式表给大量元素修改 CSS
使用 $.data 而不是 $.fn.data
别费时间在空白的选择结果上了
变量定义
条件判断
别把 jQuery 当作黑盒子
10. 代码组织
概述
关键概念
封装
对象原语
组件模式
管理依赖
获取RequireJS
在jQuery中使用RequireJS
通过RequireJS创建可重用的模块
代码优化:RequireJS构建工具
练习
创建Portlet模块
11. Custom Events
Introducing Custom Events
A Sample Application

List of Examples

1.1. 页面内嵌JavaScript的例子
1.2. 引用外部JavaScript的例子
1.3. 例子的样式
2.1. 一个简单的变量声明
2.2. 引号外的空格和制表符是没有意义的
2.3. 括号内的表达式拥有更高的优先级
2.4. 制表符只是为了增加代码可读性,并没有什么其他的作用
2.5. 连接字符串
2.6. 乘法和除法
2.7. 自加和自减
2.8. 如果把数字和字符串相加,最后得到字符串
2.9. 把字符串强制转换成数字类型
2.10. 使用一元运算符的+号,把运算符后的变量转换成数字类型。
2.11. 逻辑与和逻辑或操作符
2.12. 比较运算符
2.13. 流程控制
2.14. 被判断义为 true的值
2.15. 被判定为false的值
2.16. 三元运算符
2.17. 一个switch分支语句
2.18. 循环语句
2.19. 典型的for 循环
2.20. 典型的 while循环
2.21. while 循环,在条件判断部分对计数器i进行自加
2.22. 一个 do-while 循环
2.23. 停止循环
2.24. 跳过下一个循环迭代
2.25. 简单数组
2.26. 通过索引访问数组项
2.27. 测试数组长度
2.28. 改变数组项值
2.29. 加入元素到数组中
2.30. 关于数组
2.31. 创建一个对象常量
2.32. 函数申明
2.33. 命名函数表达式
2.34. 一个简单函数
2.35. 函数返回一个值
2.36. 函数返回另一个函数
2.37. 一个自执行的匿名函数
2.38. 以参数的方式传递一个匿名函数
2.39. 以参数的方式传递一个命名函数
2.40. 测试各种变量的类型
2.41. 函数访问同一作用域内定义的变量
2.42. 代码作用域外定义的变量不能访问该变量Code outside the scope in which a variable was defined does not have access to the variable
2.43. 拥有相同名字的变量可以以不同的值存在不同的作用域中
2.44. 函数定义后可看见变量值的改变
2.45. Scope insanity
2.46. 如何锁定i的值?
2.47. 以一个闭包的方式锁定i的值
3.1. A $(document).ready() block
3.2. $(document).ready() 简写
3.3. 用命名函数替代匿名函数传递
3.4. 通过ID选择元素
3.5. 通过类名选择元素
3.6. 通过属性选择元素
3.7. 通过组合CSS选择器选择元素
3.8. 伪选择器
3.9. 测试选择器是否包含多个元素
3.10. 在变量中保存选择器
3.11. 改善选择器
3.12. 使用表单相关的伪选择器
3.13. 链式
3.14. 格式化链式代码
3.15. 使用 $.fn.end恢复你原来的选择器
3.16. $.fn.html 方法作为一个 setter 使用
3.17. html 方法作为一个 getter 使用
3.18. 取得 CSS 属性
3.19. Setting CSS properties
3.20. 使用类
3.21. 基本尺寸方法
3.22. 设置属性
3.23. 取得属性
3.24. 使用遍历方法围绕DOM移动
3.25. 通过选择器迭代
3.26. 改变一个元素的HTML
3.27. 使用不同的方法移动元素
3.28. 拷贝一个元素
3.29. 创建新元素
3.30. 创建一个包含属性对象的新元素
3.31. 从页中获得一个新元素
3.32. 同一时刻创建和添加元素到页面
3.33. 操作单一属性
3.34. 操作多个属性
3.35. 使用函数去确定新的属性值
4.1. 校验任意值的类型e
4.2. 存储和检索一个元素的相关数据
4.3. 使用 $.fn.data 存储元素之间的关系
4.4. 将 jQuery 设为 no-conflict 模式
4.5. 在自执行匿名函数中使用 $
5.1. 使用便捷的方式绑定
5.2. 使用$.fn.bind方法绑定事件
5.3. 使用$.fn.bind方法将数据绑定到事件
5.4. 使用$.fn.one方法转换处理函数
5.5. 取消选择器下所有的点击事件处理函数绑定
5.6. 取消特定点击事件处理函数绑定
5.7. 命名空间事件 Namespacing events
5.8. 阻止链接被访问
5.9. 以正确的方式触发事件处理函数
5.10. 使用$.fn.delegate委托事件
5.11. 使用$.fn.live委托事件
5.12. 解除委托事件的绑定
5.13. hover助手函数
5.14. toggle助手函数
6.1. 基本的动画效果
6.2. 设置动画效果持续时间
6.3. 增加 jQuery.fx.speeds 自定义动画速度
6.4. 动画执行完后运行代码
6.5. 运行回调函数即使没设置任何参数
6.6. 使用$.fn.animate自定义效果
6.7. easing属性
7.1. Using the core $.ajax method
7.2. Using jQuery's Ajax convenience methods
7.3. Using $.fn.load to populate an element
7.4. Using $.fn.load to populate an element based on a selector
7.5. Turning form data into a query string
7.6. Creating an array of objects containing form data
7.7. Using YQL and JSONP
7.8. Setting up a loading indicator using Ajax Events
8.1. 这个插件用于 hover(鼠标划过)时添加/删除一个 class
8.2. Mike Alsup 的 jQuery 插件设计模式
8.3. 用 jQuery UI widget factory 创建一个简单的有状态的插件
8.4. 给 widget 传递参数
8.5. 给 widget 设置默认值
8.6. 创建 widget 的方法
8.7. 调用插件实例的方法
8.8. 当参数被修改时执行一些操作
8.9. 提供回调功能让用户进行扩展
8.10. 绑定 widget 事件
8.11. 给 widget 添加 destroy 方法
10.1. 对象原语
10.2. 将对象原语用到jQuery特性中
10.3. 组件模式
10.4. 通过jQuery使用组件模式
10.5. 使用RequireJS:一个简单示例
10.6. 使用依赖的简单JavaScript文件
10.7. 定义一个没有依赖性的RequireJS模块
10.8. 定义一个有依赖性的RequireJS模块
10.9. 定义一个返回函数的RequireJS模块
10.10. RequireJS构建配置文件

jQuery正迅速地变成前端开发人员必需技能。 这本书的目的是提供jQuery JavaScript库的概述;当你完成这本书阅读的时候, 你应该能够用jQuery完成基本的任务并且有能继续学习的坚实基础。 这本书是作为课堂教学教材设计的,但你会发现它对自学也很有用。

这是一个需要动手的课程。我们将会花费一点时间说明一个概念, 然后你将会有机会去做一个与这个概念有关的练习。 可能有些练习会让你觉得烦琐;其它的可能是十足令人气馁的。 这些练习都是没有评分的; 练习的目的只是让你在工作中能轻松地解决使用jQuery常会出现的问题。 练习的示例解决方案都被包含在示例代码中。

这本书用到的代码托管在 Github的库里。 你也可以下载代码的.zip或者.tar文件,解压后在你的服务器上使用。 如果你愿意使用git,欢迎你复制或fork这个库。

逻辑运算符使用and和or对操作数进行逻辑判断


也许无法从例子中清晰的反应出来,其中或运算符( ||) 返回第一个值为真的操作数, 如果两个操作数的值都为假,则返回最后一个操作数。 类似的,与运算符( &&) 返回第一个值为假的运算符,如果两个操作数的值都为真,则返回最后一个操作数

跳到the section called “真与假” 查看更多关于什么时候判断运算结果为true 和什么时候判断运算结果为 false的资料。

Note

你有的时候会看到开发者使用以下语句代替if进行流程控制

// 当foo的值为真的时候执行doSomething来处理foo
foo && doSomething(foo);

// 如果baz为真,则把baz的值赋给bar
// 否则bar的值为函数craetBar的返回值
var bar = baz || createBar();

这种风格的代码非常的简洁和优雅,但是同时,特别对于初学者而言,往往晦涩难懂。把它放在这里是希望当你在阅读到这类代码的时候可以读懂。但是如果你还不能够理解它的本质,且没有熟练掌握的话,我们不建议你使用此类代码

有时,你需要当符合某种特定条件的时候再运行你的代码。你可以通过流程控制语句if-else,来让代码在符合某种条件的时候运行。


Note

虽然当if语句后面只跟有一行代码时,该代码不加括号也是可以运行的。但是为了代码可读性,代码前后通常也加上括号

请注意,不要在if/esle代码段中多次定义同一个函数,这会引起意想不到的结果

循环语句可以让一段代码执行一定的次数。


Example 2.18, “循环语句”,即使我们在变量名 i前使用关键字var,这也并不意味着 i 变量的作用域在 循环体内。我们将在后面的章节讨论变量的作用域。

函数是包含需要反复执行的代码块。函数可以没有参数或包含多个参数,能返回任意一个值。

函数的创建有多种方法:



在设置一个函数名时我更喜欢命名函数表达式,这有相当的深度和技术因素。 你可能在其它的 JavaScript 代码中看到过这两种方法的使用。

"Scope" 引用的变量在指定时间内的一段代码中的可用性refers to the variables that are available to a piece of code at a given time. 一个不标准的作用域将导致令人沮丧的调试体验。

当使用 var 关键字描述函数中的一个变量时,它作为那个函数中的代码仅仅是个变量 -- 而函数外的代码不能访问这个变量。在其它方面,定义为 inside 的函数 will 能访问到所描述的变量。

此外,在函数里没有使用 var 关键字描述的变量对函数来讲不是局部的 -- JavaScript 将贯穿所有作用域直到 window 作用域以发现之前定义的变量位置。如果变量之前没有定义,它将创建一个全局变量,这会是一个意想不到的结果。






这是 jQuery 最基础的概念,意思是 “选择一些元素然后通过它们做一些事” 除一些非标准的选择器外,jQuery 支持大部分的 CSS3 选择器。若要了解完整的选择器参考, 请访问 http://api.jquery.com/category/selectors/.

以下是一些通用选择技术的例子






Note

当你使用 :visible:hidden 伪选择器时, jQuery 会测试元素的真实可见性, 而不是它的CSS的可见性或显示 — 就是说, 如果physical height and width on the page 都大于零,它就可以看见. 然而,这个测试不能与 <tr> 元素一起工作; 在这种情况下, jQuery 检测 CSS display 属性, 如果 它的 display 属性被设置成 none 那就认为这个元素将隐藏。 即使 CSS 将影响它们的视觉渲染,元素也不会被添加到被隐藏的DOM中。 (请看这一章的后面操作小节有关如何创建和增加元素到DOM中的介绍。)

作为参考, 这里的jQuery代码被用于检测此元素是显示还是隐藏,为了清楚也添加了注释:

jQuery.expr.filters.hidden = function( elem ) {
    var width = elem.offsetWidth, height = elem.offsetHeight,
        skip = elem.nodeName.toLowerCase() === "tr";

    // 元素的高度为0,宽度为0, 
    // 并且它不是 <tr>?
    return width === 0 && height === 0 && !skip ?

        // 所以它必须隐藏
        true :

        // 但是如果它拥有高度和宽度值 
        // 并且它不是 <tr>
        width > 0 && height > 0 && !skip ?

            // 所以它必须显示
            false :

            // 如果我们到了这,元素拥有宽度
            // 和高度, 但它也是一个 <tr>,
            // 所以检查显示属性以
            // 确定是否隐藏
            jQuery.curCSS(elem, "display") === "none";
};

jQuery.expr.filters.visible = function( elem ) {
    return !jQuery.expr.filters.hidden( elem );
};

每次你指定了一个选择器,一段代码运行,jQuery 都不会为你做任何的 caching 工作。如果你已经指定了选择器而你又需要重新指定它时,它将在变量中保存选择器,而不是反复的指定它。


Note

Example 3.10, “在变量中保存选择器”, 变量名以一个美元符号开始。不像在其它的语言中,在 JavaScript 中没有特定的指定美元符号 -- 而是使用其它字符 。我们在这里使用它是为了表明变量包含一个 jQuery 对象。这是惯例 -- 一种 匈牙利命名法 -- 这仅仅是惯例,而不是强制性的。

一旦你保存了选择器,你就能在保存的变量中调用 jQuery 方法,就像在原来的选择器中调用一样。

Note

只有当你指定一个选择器后,选择器才会从页面取回元素。如果你添加元素到页面延迟了,你必须重新选择或将它们添加到存储在变量中的选择器中。当 DOM 改变时被保存的选择器不会自动更新。

一旦你指定一个选择器, 你就能调用它的方法。 这些方法通常带来两种不同的口味: getters 和 setters. Getters 返回第一个选择元素的属性;setters 设置所有选择元素为同一属性。

jQuery 包含便捷的方法取得和设置元素的 CSS 属性。

Note

CSS 属性通常包含一个连字符而在 JavaScript 中是 camel cased 形式。比如, CSS 属性 font-size 在 JavaScript 表示为 fontSize



参数样式的注释我们将放在第二行 -- 一个对象将包含多个属性。这是传递多个参数到函数的通用方法,更多 jQuery setter 方法接受对象一次性设置多个值。

一旦你指定了一个选择器, 乐趣就开始了。你能改变,移动,删除和克隆元素。你还能通过简单的语法创建一个新元素。

有关 jQuery 操作方法的完整文档,请访问 http://api.jquery.com/category/manipulation/.

这里有一些方法帮助你改变已有的元素。在多数通常的任务中,你能执行内部HTML或元素属性的改变。jQuery 为这些操作的有序提供了简单, 跨浏览器的方法。你也能在典型的getter中使用相同的方法获取元素信息。 我们要看到的例子将贯穿这一节,但是特殊的,这有一些你可以使用的新的方法用于获取和设置元素信息。

Note

改变这些元素是琐碎的,但请记住这些改变将作用于选择器的 all 元素, 因此如果你打算改变一个元素,在调用setter方法前请确认你的选择器。

Note

当方法作为getters使用时,它们通常只工作在选择器的第一个元素,并且不会返回 jQuery 对象,所以你不能给它们链式添加方法。一个值得注意的例外是 $.fn.text; 描述如下, 它在选择器中获得所有元素的文档。

$.fn.html

获取或设置html文档。

$.fn.text

获取或设置text文档;HTML将被剥离。

$.fn.attr

获取和设置提供属性的值

$.fn.width

获取或设置作为整型选择器的第一个元素的pixels宽度。

$.fn.height

获取或设置作为整型选择器的第一个元素的pixels高度。

$.fn.position

为选择器中的第一个元素取得包含位置信息的对象,相对于它们第一个位置的祖先。这只有一个getter

$.fn.val

获取或设置form元素的值。


这里有一些移动DOM元素的方法;通常,这里有两个途径:

  • 放置与另外元素有关的选择元素Place the selected element(s) relative to another element

  • 放置一个与选择元素有关的元素Place an element relative to the selected element(s)

在例子中, jQuery 提供了 $.fn.insertAfter$.fn.after. $.fn.insertAfter 方法在你提供了参数的元素后面放置一个选择元素;$.fn.after 方法在选择元素后面放置一个带参数的元素。其它的方法允许这样的模式:$.fn.insertBefore and $.fn.before; $.fn.appendTo and $.fn.append; and $.fn.prependTo and $.fn.prepend.

这个方法为你进行的更多检测将依赖于那些你已经选择的元素,是否你将需要保存那些你加入到页面的元素引用。如果你需要保存引用,你也将生效第一次处理 -- 放置与其它元素有关的选择元素 -- 在这个例子中,作为你放置元素的返回值。 $.fn.insertAfter, $.fn.insertBefore, $.fn.appendTo, and $.fn.prependTo will be your tools of choice.


jQuery 提供了一个简单而优雅的途径,使用与你选择器同样的方法 $() 来创建新元素。



注意我们包含在第二个参数中的属性对象,属性名类被引用,而属性名的文本和链接却不是。属性名通常不被引用, 除非它们作为保留字(在这个例子中是作为类)。

当你创建一个新元素,它不会立刻添加到你的页面。一旦它被创建这里会有几个方法添加一个元素到页面里。


严格地讲,你不必在变量中保存创建的元素 -- 你能直接在$()后调用此方添加元素到页面。然后, 大多数时间你将引用添加的元素,而不需要在后面选择它。

你甚至能在添加元素的时候创建它,但这个例子请注意你不能获得新创建的元素的引用。


Note

在页面创建新元素的语法非常简单,模版会忽略在反复添加到DOM时所产生的巨大性能代价。如果你添加更多的元素到同一容器里,你将连接所有的html到单一的字符串中,接着追加字符到容器里替换掉一次一个的元素追加方式。你能使用一个数组收集所有的片段,然后 join 它们追加到同一字符串中。

var myItems = [], $myList = $('#myList');

for (var i=0; i<100; i++) {
    myItems.push('<li>item ' + i + '</li>');
}

$myList.append(myItems.join(''));

jQuery 在 $ 命名空间中提供了多个实用方法。这些方法能帮助你完成例程(routine programming)任务。接下来有一些实用方法的例子;完整的 jQuery 实用方法的参考, 请访问 http://api.jquery.com/category/utilities/.

$.trim

去除行间和末尾的空白。

$.trim('    大量额外空白    ');
// 返回 '大量额外空白'
$.each

在数组和对象上迭代。

$.each([ 'foo', 'bar', 'baz' ], function(idx, val) {
    console.log('element ' + idx + 'is ' + val);
});

$.each({ foo : 'bar', baz : 'bim' }, function(k, v) {
    console.log(k + ' : ' + v);
});

Note

还有一个方法 $.fn.each,也可用于在一个选择的元素中进行迭代。

$.inArray

在数组中返回值索引,如果值不在数组中就为 -1 。

var myArray = [ 1, 2, 3, 5 ];

if ($.inArray(4, myArray) !== -1) {
    console.log('found it!');
}
$.extend

使用后面对象的属性改变第一个对象的属性。

var firstObject = { foo : 'bar', a : 'b' };
var secondObject = { foo : 'baz' };

var newObject = $.extend(firstObject, secondObject);
console.log(firstObject.foo); // 'baz'
console.log(newObject.foo);   // 'baz'

如果你不打算改变任何对象你可传递 $.extend,作为第一个参数传递一个空对象。

var firstObject = { foo : 'bar', a : 'b' };
var secondObject = { foo : 'baz' };

var newObject = $.extend({}, firstObject, secondObject);
console.log(firstObject.foo); // 'bar'
console.log(newObject.foo);   // 'baz'
$.proxy

返回一个函数它总是运行在一个提供的作用域里 — 就是说,this 的设置意味着内部传递函数到第二个参数。

var myFunction = function() { console.log(this); };
var myObject = { foo : 'bar' };

myFunction(); // 记录 window 对象

var myProxyFunction = $.proxy(myFunction, myObject);
myProxyFunction(); // 记录 myObject 对象

如果你拥有一个带方法的对象,你能传递对象和方法名去返回一个总运行在对象作用域里的函数。

var myObject = {
    myFn : function() {
        console.log(this);
    }
};

$('#foo').click(myObject.myFn); // 记录 DOM 元素 #foo
$('#foo').click($.proxy(myObject, 'myFn')); // 记录 myObject

作为你在 jQuery 的工作进展, As your work with jQuery progresses, 你将发现这里常有一些你准备作为元素要存储的数据。you'll find that there's often data about an element that you want to store with the element. 在 JavaScript 里,In plain JavaScript, 你可能直接将属性加入到 DOM 元素中,you might do this by adding a property to the DOM element, 但是你必须处理在一些浏览器中的内存泄露。but you'd have to deal with memory leaks in some browsers. jQuery 提供了一个简洁的方法存储相关数据到元素中,它帮你管理内存问题。offers a straightforward way to store data related to an element, and it manages the memory issues for you.


你能存储一个元素任何种类的数据,当你进行一个复杂应用开发时它们的重要性难以用语言表达 。这一课的目的,我们将使用 $.fn.data 来存储其它元素的引用。

在这的例子中,我们可能需要在一组列表项和它的内部划分上建立一种关系。我们建立的这个关系在每一个单独时间里与这些条目相互作用,但是一个更好的解决方案是一旦这个关系创建好,通过使用$.fn.data: 来存储划分条目的指针and then store a pointer to the div on the list item using $.fn.data:


除了传递 $.fn.data 一个 key-value 对到存储数据外,你也能传递一个包含一个或更多键值对的对象。

尽管 jQuery 消除了大部分 JavaScript 在浏览器间的差异性,这里仍有很多场合是你的代码需要知道浏览器的环境 there are still occasions when your code needs to know about the browser environment.

jQuery 提供的 $.support 对象, 和 $.browser 对象一样被废弃。关于这些对象的完整文档,请访问 http://api.jquery.com/jQuery.support/http://api.jquery.com/jQuery.browser/.

$.support 对象主要用于检测浏览器所支持的功能;在为不同浏览器定制 JavaScript 代码时它被推荐作为“future-proof”方法来使用。

虽然不赞成使用 $.browser 对象而鼓励使用 $.support 对象,但它仍保留在 jQuery 中而没删除。它提供了直接检测浏览器类型和版本的功能。

jQuery为常用的事件提供了简便的绑定方法,这些方法是日后最常用的方法。 这些方法是jQuery的$.fn.bind方法的简便缩写, 例如$.fn.click$.fn.focus$.fn.blur$.fn.change等。 bind这个方法一般用于绑定相同的事件处理函数到多个事件, 也用于为事件处理函数提供数据,或者在使用自定义事件时使用。




你将会频繁地使用jQuery添加新的元素到页面中,你将有可能需要绑定一些已经存在在页面上的事件到这些新元素上。 相对于在为页面添加元素的时候重复绑定事件,你可以使用事件委托。 通过事件委托将事件绑定到容器元素,当事件被触发的时候,可以知道事件是在哪个容器元素被触发。 如果这听起来很复杂,幸运地jQuery提供了$.fn.live$.fn.delegate方法让这个过程变得很简单。

当大多数的人在处理后来才被添加到页面的元素时才使用委托,就算你从不添加更多的元素到页面,委托也有性能上的优势。 绑定事件处理器到成百上千的不同元素所需的时间并不是小数目,如果你有很多元素,你应该考虑将相关的事件委托到一个容器元素中。

Note

从jQuery 1.3开始提供了$.fn.live方法,在1.3版本这个方法只支持特定的事件类型。 自从jQuery 1.4.2开始提供$.fn.delegate方法,应优先选择这个方法。



jquery常用的基本效果方法:

$.fn.show

显示匹配的元素

$.fn.hide

隐藏匹配的元素

$.fn.fadeIn

通过淡入的方式显示匹配元素

$.fn.fadeOut

通过淡出的方式显示匹配元素

$.fn.slideDown

通过高度变化(向下增大)用滑动动画显示一个匹配元素

$.fn.slideUp

通过高度变化(向上减小)用滑动动画隐藏一个匹配元素

$.fn.slideToggle

根据当前的元素display属性,用滑动动画显示或隐藏一个匹配元素


Proper use of Ajax-related jQuery methods requires understanding some key concepts first.

While jQuery does offer many Ajax-related convenience methods, the core $.ajax method is at the heart of all of them, and understanding it is imperative. We'll review it first, and then touch briefly on the convenience methods.

I generally use the $.ajax method and do not use convenience methods. As you'll see, it offers features that the convenience methods do not, and its syntax is more easily understandable, in my opinion.

jQuery’s core $.ajax method is a powerful and straightforward way of creating Ajax requests. It takes a configuration object that contains all the instructions jQuery requires to complete the request. The $.ajax method is particularly valuable because it offers the ability to specify both success and failure callbacks. Also, its ability to take a configuration object that can be defined separately makes it easier to write reusable code. For complete documentation of the configuration options, visit http://api.jquery.com/jQuery.ajax/.


Note

A note about the dataType setting: if the server sends back data that is in a different format than you specify, your code may fail, and the reason will not always be clear, because the HTTP response code will not show an error. When working with Ajax requests, make sure your server is sending back the data type you're asking for, and verify that the Content-type header is accurate for the data type. For example, for JSON data, the Content-type header should be application/json.

There are many, many options for the $.ajax method, which is part of its power. For a complete list of options, visit http://api.jquery.com/jQuery.ajax/; here are several that you will use frequently:

async

Set to false if the request should be sent synchronously. Defaults to true. Note that if you set this option to false, your request will block execution of other code until the response is received.

cache

Whether to use a cached response if available. Defaults to true for all dataTypes except "script" and "jsonp". When set to false, the URL will simply have a cachebusting parameter appended to it.

complete

A callback function to run when the request is complete, regardless of success or failure. The function receives the raw request object and the text status of the request.

context

The scope in which the callback function(s) should run (i.e. what this will mean inside the callback function(s)). By default, this inside the callback function(s) refers to the object originally passed to $.ajax.

data

The data to be sent to the server. This can either be an object or a query string, such as foo=bar&baz=bim.

dataType

The type of data you expect back from the server. By default, jQuery will look at the MIME type of the response if no dataType is specified.

error

A callback function to run if the request results in an error. The function receives the raw request object and the text status of the request.

jsonp

The callback name to send in a query string when making a JSONP request. Defaults to "callback".

success

A callback function to run if the request succeeds. The function receives the response data (converted to a JavaScript object if the dataType was JSON), as well as the text status of the request and the raw request object.

timeout

The time in milliseconds to wait before considering the request a failure.

traditional

Set to true to use the param serialization style in use prior to jQuery 1.4. For details, see http://api.jquery.com/jQuery.param/.

type

The type of the request, "POST" or "GET". Defaults to "GET". Other request types, such as "PUT" and "DELETE" can be used, but they may not be supported by all browsers.

url

The URL for the request.

The url option is the only required property of the $.ajax configuration object; all other properties are optional.

If you don't need the extensive configurability of $.ajax, and you don't care about handling errors, the Ajax convenience functions provided by jQuery can be useful, terse ways to accomplish Ajax requests. These methods are just "wrappers" around the core $.ajax method, and simply pre-set some of the options on the $.ajax method.

The convenience methods provided by jQuery are:

$.get

Perform a GET request to the provided URL.

$.post

Perform a POST request to the provided URL.

$.getScript

Add a script to the page.

$.getJSON

Perform a GET request, and expect JSON to be returned.

In each case, the methods take the following arguments, in order:

url

The URL for the request. Required.

data

The data to be sent to the server. Optional. This can either be an object or a query string, such as foo=bar&baz=bim.

Note

This option is not valid for $.getScript.

success callback

A callback function to run if the request succeeds. Optional. The function receives the response data (converted to a JavaScript object if the data type was JSON), as well as the text status of the request and the raw request object.

data type

The type of data you expect back from the server. Optional.

Note

This option is only applicable for methods that don't already specify the data type in their name.


jQuery’s ajax capabilities can be especially useful when dealing with forms. The jQuery Form Plugin is a well-tested tool for adding Ajax capabilities to forms, and you should generally use it for handling forms with Ajax rather than trying to roll your own solution for anything remotely complex. That said, there are a two jQuery methods you should know that relate to form processing in jQuery: $.fn.serialize and $.fn.serializeArray.



一个典型的插件的代码如下:

(function($){
    $.fn.myNewPlugin = function() {
        return this.each(function(){
            // 一堆代码...
        });
    };
}(jQuery));

那几行代码,没把你搞晕吧? 其实里面的重点就是扩展 jQuery 的 prototype 对象,看下面这行:

$.fn.myNewPlugin = function() { //...

这里只是将它放在一个即时执行的函数内部:

(function($){
    //...
}(jQuery));

这样做是为了营造一个私有的 scope,我们可以在这个私有 scope 内部安全地使用 $ 扩展 jQuery, 而不用担心 $ 会被其他 js 库的改写。

所以,这里实际的插件代码是这样的:

$.fn.myNewPlugin = function() {
    return this.each(function(){
        // 一堆代码...
    });
};

新插件中,关键字 this 指向的是调用这个插件的 jQuery 对象。

var somejQueryObject = $('#something');

$.fn.myNewPlugin = function() {
    alert(this === somejQueryObject);
};

somejQueryObject.myNewPlugin(); // alerts 'true'

典型的 jQuery 对象是包含任意多个的 DOM 对象的, 这也是为什么 jQuery 对象被看作是 DOM 对象集的原因。

因此,针对对象集做操作需要遍历它, 通过 jQuery 的 each() 方法我们很容易地实现了。

$.fn.myNewPlugin = function() {
    return this.each(function(){
    
    });
};

与 jQuery 其它方法一样,each() 方法也是返回一个 jQuery 对象, 这成就了我们所熟知并深爱的链式写法($(...).css().attr()...)。 我们不应该破坏这种惯例,于是乎,我们也应该返回一个 this 对象。 在循环内部,你可以对每一个元素做你要做的事。就上面所讨论的, 下面是一个小插件的例子。

(function($){
    $.fn.showLinkLocation = function() {
        return this.filter('a').each(function(){
            $(this).append(
                ' (' + $(this).attr('href') + ')'
            );
        });
    };
}(jQuery));
    
// 使用样例:
$('a').showLinkLocation();

这个小巧的插件可以将集合中所有的链接的 href 属性值包含在括号 中并将它作为该元素的内容显示出来。

<!-- 调用插件之前: -->
<a href="page.html">Foo</a>
    
<!-- 之后: -->
<a href="page.html">Foo (page.html)</a>

虽然简单,但这个插件还是可以优化一下:

(function($){
    $.fn.showLinkLocation = function() {
        return this.filter('a').append(function(){
              return ' (' + this.href + ')';
        });
    };
}(jQuery));

我们利用了,append 方法是可以接受一个回调函数作为参数, 并将回调函数的返回值作为要添加的内容的这一点。同时,也可以注意到, 我们不是通过 attr 方法去获取 href 的属性值, 而是直接使用了内置的 DOM API,访问元素的 href 属性来获取。

下面是另一个例子。它不需要我们用 each() 方法去循环遍历, 只是简单地直接委派给另一个 jQuery 方法:

(function($){
    $.fn.fadeInAndAddClass = function(duration, className) {
        return this.fadeIn(duration, function(){
            $(this).addClass(className);
        });
    };
}(jQuery));
    
// 使用方法:
$('a').fadeInAndAddClass(400, 'finishedFading');

有时候,你的代码中需要一块功能的支持,比如, 你希望用一个方法将对 jQuery 对象的一长串操作封装起来, 那么,你会需要一个自己编写的插件。

大部分插件都是简单地在 $.fn 名字空间下添加方法。 jQuery 确保了在调用的方法内部 this 所指向的就是调用该方法的 jQuery 对象。 反过来,你的插件也应该确保返回同一的对象,除非是在文档里明确声明了。

下面是一个简单插件的例子:


想要了解更多插件开发的内容,可以阅读 Mike Alsup 撰写的 A Plugin Development Pattern。文中讲述了他开发的一个叫 $.fn.hilight 的插件, 用于对在有 metadata 插件时提供一些支持,和一个统一设置插件的全局和实例参数的方法。


虽然大多数的 jQuery 插件都是无状态的(stateless),也就是说, 与插件进行交互的就限于调用插件时的那一组对象, 但是有好大一部分功能需求没办法通过这种简单的插件模式来实现。

为了填补这一空白,jQuery UI 实现一套更加先进的插件系统。 它可以管理状态,允许通过一个插件暴露多个函数,并提供多个扩展点。 这套系统被称为 widget factory,对应 jQuery.widget, 也是 jQuery UI 1.8 的一部分。不过,它是可以独立于 jQuery UI 使用的。

我们接下来创建一个简单的进度条插件,用来演示 widget factory 的能力。

我们首先创建一个只能设置一次的进度条。 下面是实现代码,使用 jQuery.widget 创建一个插件。 它接受两个参数,插件名字和带有具体实现方法的对象。 当插件被调用时,它会创建一个新的插件实例,而插件方法的执行对象也就是那个实例。 这与标准 jQuery 插件实现有两点是很不一样的。一是,执行者是对象而不是 DOM 元素; 二是,执行者永远是单个对象,而不是元素集。


插件名字必须包含一个命名空间,这里我们用了 nmk 这个命名空间。 但这个命名空间有个限制——只允许一层,也就是说,我们不能使用像 nmk.foo 这样的命名空间。另外可以看到 widget factory 给我们提供了两个属性。一是 this.element, 它指向一个只包含一个元素的 jQuery 对象,如果插件是由包含多个元素的 jQuery 对象调用时,会给其中的每一个元素都分配一个插件实例, 并让 this.element 指向它;二是 this.options, 是包含键值对形式的插件参数的 hash 对象,插件的参数就是像这样传递进来的。

Note

本例中使用了 nmk 作为命名空间。 命名空间 ui 则是保留给官方 jQuery UI 插件的。 创建自己的插件的时候,应该使用自有的命名空间的, 这样可以让人一看就清楚这插件哪来的,是否是一个大体系的一部分。


当我们调用 jQuery.widget 时,与创建标准插件的方式一样, 它也是通过往 jQuery.fn 上面添加方法的方式来扩展 jQuery 对象。 而那个方法的名称就是我们定义的插件名称除去命名空间的部分,案例中是 jQuery.fn.progressbar。调用时所传递的参数会传递给插件实例的 this.options。在下面的代码中,我们可以在参数中设置一些默认值。 在设计 API 的时候,你应该先搞清楚最通常的用例,并据此设定相应的默认参数, 那么这些参数就成为可选项了。


接下来就要初始化进度条了。我们使它可以通过调用插件实例方法的方式来执行一些操作。 要给插件定义方法,只需要将其实现代码放在定义体内即可。 我们也可以通过在方法名前加下划线的方式来定义“私有”方法。


将方法名作为参数传进去即可调用插件实例的方法。 如果调用的方法需要传递参数,只需要将那些参数作为后续参数一同传递。


Note

初始化用所用的 jQuery 方法,向它传递方法名就可以执行方法,这看起来似乎很奇怪。 但这样可以在维持原来的链式调用的方式的同时,防止 jQuery 命名空间被污染。

扩展插件的一个最简单的办法就是添加回调功能, 这样使用者就可以根据插件状态的改变来采取行动。下面,我们来尝试添加一个回调功能, 在进度达到 100% 时触发。_trigger 方法介绍三个参数: 回调名称,触发回调的本地事件对象以及相关的数据。虽然其中只有回调名称是必须的, 不过其它参数对使用者来说挺有用的。比如说,创建一个可拖拽插件, 我们可以在触发回调时将原生的 mouseover 事件对象传递过去, 用户在回调函数里就可以根据这个对象中的 x/y 坐标对拖拽进行一些处理。


回调函数实际上只是另外一种参数,因此你也可以像其它参数一样进行查询和修改了。 无论回调函数是否设置,事件都会触发的。事件类型则是由插件名称和回调名称合并而成。 回调和事件被触发时会收到同样的两个参数:事件对象和相关数据。可以看下面的例子。

如果你的插件提供些功能是允许用户阻止操作的,最好的方式就是提供一个可撤销的回调。 用户可以像撤销原生事件那样,调用 event.preventDefault() 或者 return false,去撤销回调和相关的事件。如果用户撤销了回调,_trigger 方法会返回 false, 在插件中就可以据此采取相应的动作。


请访问http://github.com/rmurphey/jqfundamentals 并做出贡献!

本章涵盖一些 jQuery 和 JavaScript 的最佳实践(排名不分先后), 而其中有许多是基于 Paul Irish 的演讲 jQuery Anti-Patterns for Performance

代码组织的第一步是将应用程序分离成不同的代码块;有时,哪怕只做到这一点也是很有意义的。

对象原语可能是最简单的关联代码封装方式了。它不会为属性或方法提供任何隐私保护,但它对于从你的代码中消除匿名函数,集中配置选项,简化重用和重构都很有帮助。


上面的对象原语简单地将一个对象分配给了一个变量。该对象具有一个属性和几个方法。所有的属性和方法都是公有的,因此你应用程序的任何部分都可以访问对象的属性、调用对象的方法。尽管有一个init方法,但在对象实例化之前调用它却没有任何必要。

我们怎样将这种模式应用到jQuery代码中?让我们通过编写传统jQuery风格的代码来说明吧:

//点击一个列表项加载一些内容
//引用列表项的ID并隐藏兄弟列表项的内容
$(document).ready(function() {
  $('#myFeature li')
    .append('<div/>')
    .click(function() {
      var $this = $(this);
      var $div = $this.find('div');
      $div.load('foo.php?item=' +
        $this.attr('id'), 
        function() {
          $div.show();
          $this.siblings()
            .find('div').hide();
        }
      );
    });
});

如果它出现在我们的应用程序范围内,抛弃它可能会更好。换句话说,如果它是一个大型应用程序的一部分,我们最好将这个功能从孤立的功能中分离出来。我们可能想把代码中的URL移到一个配置区域。最后,我们可能想摆脱链式代码的束缚让以后修改功能块更简单一点。


你会关心的第一件事就是这种方式比原来的代码明显要长得多——此外,如果它在我们应用程序的范围内,对象原语的使用则有点泛滥了。假设它不在我们程序的范围内,我们便发现了这样几个事实: The first thing you'll notice is that this approach is obviously far longer than the original — again, if this were the extent of our application, using an object literal would likely be overkill. Assuming it's not the extent of our application, though, we've gained several things:

  • 我们已经将功能特性分解成了短小的方法。如果以后我们需要改变显示内容,在哪里更改将一目了然。

  • 我们已经消除了匿名函数的使用。

  • 我们已经将配置选项移出了代码本身,并将这些配置选项放到了核心位置。

  • 我们已经摆脱了链式代码的束缚,使代码易于重构,重新合成,重新组织。

作为不一般的特性,对象原语是对在$(document).ready()块中在编写冗长代码的明显改进,它迫使我们考虑应用程序的功能块。无论如何,和在$(document).ready()块中简单定义大量函数相比,它们也好不到哪里去。

组件模式突破了对象原语的一些局限性,如有必要可以对外暴露出公有API,为变量和函数提供隐私保护。


在上面的例子里,我们自动执行了一个返回对象的匿名函数。在该函数内部,我们定义了一些变量,因为这些变量是定义在函数内部的,我们无法在函数之外去访问它们,除非我们把它放到返回的对象中。这意味着函数外部的代码无法访问privateThing变量或changePrivateThing函数。不管怎样,sayPrivateThing却可以访问privateThingchangePrivateThing,因为它们和sayPrivateThing处于相同的作用域。

这种模式之所以十分强大,是因为当需要暴露由返回对象的属性和方法构成的受限API时,你可以隐藏这些变量名,形成私有的变量和函数。

下面是上一个例子的修正版本,它向我们展示了仅仅暴露该组件一个公有方法showItemByIndex()时,通过组件模式创建相同的特性。


当项目达到一定规模时,管理项目的脚本模块开始变得非常棘手。你显然需要按正确的次序组织好这些脚本,并且开始认真考虑将脚本打包部署,只让一个或少数请求去载入这些脚本。你或许还想在页面加载完成后立即加载这些脚本代码。 When a project reaches a certain size, managing the script modules for a project starts to get tricky. You need to be sure to sequence the scripts in the right order, and you need to start seriously thinking about combining scripts together into a bundle for deployment, so that only one or a very small number of requests are made to load the scripts. You may also want to load code on the fly, after page load.

RequireJS是James Burke设计的一个依赖管理工具,它可以帮助你管理脚本模块并以正确的次序加载脚本,不需要改变标记即可以通过RequireJS优化工具轻松组合后续的脚本。它提供一种简单的方式在页面加载完成后载入脚本,随着时间的推移可以有效扩充脚本文件的尺寸。

RequireJS的模块系统可以帮助你定义作用域良好的模块,即使你却不关注这套系统也能获得依赖管理和构建时优化的好处。如果你开始创建模块化的代码并在少数场合重用它们,久而久之,RequireJS的模块格式会使编写联机加载的封装代码十分简单。特别是你想使用国际化字符串,让你的项目针对不同语言进行本地化,或者加载HTML字符串并确保在执行代码前这些字符串确实有效,或者只是把JSONP服务当作依赖关系来使用,RequireJS都是你的得力伙伴。

在jQuery中使用RequireJS最简单的方式是下载一个内建RequireJS的jQuery构建版本。这个构建版本不包括RequireJS和jQuery功能发生重复的部分。你还会发现下载使用RequireJS的jQuery示例项目对你也很有帮助。

通过require.def(),RequireJS让定义可重用的模块变得十分简单。RequireJS模块可以使用依赖来定义一个模块,并且Require模块可以返回一个值——不管是一个对象还是一个函数——并让其它模块来使用。

如果你的模块没有任何依赖性,只需要指定一个模块名称作为第一个参数调用require.def()。第二个参数只是一个定义了模块属性的对象原语。例如:

Example 10.7. 定义一个没有依赖性的RequireJS模块

require.def("my/simpleshirt",
    {
        color: "black",
        size: "unisize"
    }
);

这个示例将被放到my/simpleshirt.js文件中。

如果你的模块存在依赖性,你可以将依赖关系作为第二个参数传递给require.def()(用数组的方式),接着传递一个函数作为第三个参数。这个函数将在所有依赖关系加载完成后被调用,以便定义这个模块。


在这个例子里创建了my/shirt模块,它依赖于my/cart和my/inventory。在磁盘上,这些文件被构造成这样:

my/cart.js
my/inventory.js
my/shirt.js

定义my/shirt的函数在my/cartmy/inventory加载前不会被调用,函数以cartinventory参数接收模块。函数参数的顺序必须和依赖关系数组中的依赖关系次序一致。调用函数后返回的对象定义了my/shirt模块。通过这种方式定义模块,my/shirt不会作为一个全局对象存在。在模块中定义全局变量被明确地阻止,所以同一时间可以在同一个页面使用多个不同版本的模块。

模块中返回对象不是必需的;允许从函数中返回任何有效值。


每个JavaScript文件必须只有唯一的一个模块。


一旦你结合RequireJS进行依赖管理,实施页面优化将会非常简单。下载RequireJS源文件并放到你喜欢的任何地方,最好是放到web开发以外的区域。因为这个例子的原因,RequireJS源文件被放到了webapp的同一级目录,它包括了HTML页面和包含所有脚本的目录。完整的目录结构如下:

requirejs/ (用于构建工具)
webapp/app.html
webapp/scripts/app.js
webapp/scripts/require-jquery.js
webapp/scripts/jquery.alpha.js
webapp/scripts/jquery.beta.js

接着,在这个拥有require-jquery.js和app.js的脚本目录里,创建叫作app.build.js的文件,并且具有下列内容:


要使用这个构建工具,你需要安装Java 6。闭包编译器用于JavaScript压缩环节(如果optimize: "none"被注释掉),它需要Java 6的支持。

要开始进行构建工作,需进入webapp/scripts目录,执行下列命令:

# 非Windows系统
../../requirejs/build/build.sh app.build.js

# Windows系统
..\..\requirejs\build\build.bat app.build.js

现在,webapp-build目录中将会生成app.jsjquery.alpha.jsjquery.beta.js这样几个文件。如果在浏览器中打开webapp-build目录中的app.html文件,你不会看到任何加载jquery.alpha.jsjquery.beta.js的网络请求。

We’re all familiar with the basic events — click, mouseover, focus, blur, submit, etc. — that we can latch on to as a user interacts with the browser. Custom events open up a whole new world of event-driven programming. In this chapter, we’ll use jQuery’s custom events system to make a simple Twitter search application.

It can be difficult at first to understand why you'd want to use custom events, when the built-in events seem to suit your needs just fine. It turns out that custom events offer a whole new way of thinking about event-driven JavaScript. Instead of focusing on the element that triggers an action, custom events put the spotlight on the element being acted upon. This brings a bevy of benefits, including:

  • Behaviors of the target element can easily be triggered by different elements using the same code.

  • Behaviors can be triggered across multiple, similar, target elements at once.

  • Behaviors are more clearly associated with the target element in code, making code easier to read and maintain.

Why should you care? An example is probably the best way to explain. Suppose you have a lightbulb in a room in a house. The lightbulb is currently turned on, and it’s controlled by two three-way switches and a clapper:

<div class="room" id="kitchen">
    <div class="lightbulb on"></div>
    <div class="switch"></div>
    <div class="switch"></div>
    <div class="clapper"></div>
</div>

Triggering the clapper or either of the switches will change the state of the lightbulb. The switches and the clapper don’t care what state the lightbulb is in; they just want to change the state.

Without custom events, you might write some code like this:

$('.switch, .clapper').click(function() {
    var $light = $(this).parent().find('.lightbulb');
    if ($light.hasClass('on')) {
        $light.removeClass('on').addClass('off');
    } else {
        $light.removeClass('off').addClass('on');
    }
});

With custom events, your code might look more like this:

$('.lightbulb').bind('changeState', function(e) {
    var $light = $(this);
    if ($light.hasClass('on')) {
        $light.removeClass('on').addClass('off');
    } else {
        $light.removeClass('off').addClass('on');
    }
});

$('.switch, .clapper').click(function() { 
    $(this).parent().find('.lightbulb').trigger('changeState');
});

This last bit of code is not that exciting, but something important has happened: we’ve moved the behavior of the lightbulb to the lightbulb, and away from the switches and the clapper.

Let’s make our example a little more interesting. We’ll add another room to our house, along with a master switch, as shown here:

<div class="room" id="kitchen">
    <div class="lightbulb on"></div>
    <div class="switch"></div>
    <div class="switch"></div>
    <div class="clapper"></div>
</div>
<div class="room" id="bedroom">
    <div class="lightbulb on"></div>
    <div class="switch"></div>
    <div class="switch"></div>
    <div class="clapper"></div>
</div>
<div id="master_switch"></div>

If there are any lights on in the house, we want the master switch to turn all the lights off; otherwise, we want it to turn all lights on. To accomplish this, we’ll add two more custom events to the lightbulbs: turnOn and turnOff. We’ll make use of them in the changeState custom event, and use some logic to decide which one the master switch should trigger:

$('.lightbulb')
    .bind('changeState', function(e) {
        var $light = $(this);
        if ($light.hasClass('on')) {
            $light.trigger('turnOff');
        } else {
            $light.trigger('turnOn');
        }
    })
    .bind('turnOn', function(e) {
        $(this).removeClass('off').addClass('on');
    })
    .bind('turnOff', function(e) {
        $(this).removeClass('off').addClass('on');
    });
 
$('.switch, .clapper').click(function() { 
    $(this).parent().find('.lightbulb').trigger('changeState');
});
 
$('#master_switch').click(function() {
    if ($('.lightbulb.on').length) {
        $('.lightbulb').trigger('turnOff');
    } else {
        $('.lightbulb').trigger('turnOn');
    }
});

Note how the behavior of the master switch is attached to the master switch; the behavior of a lightbulb belongs to the lightbulbs.

Note

If you’re accustomed to object-oriented programming, you may find it useful to think of custom events as methods of objects. Loosely speaking, the object to which the method belongs is created via the jQuery selector. Binding the changeState custom event to all $(‘.light’) elements is akin to having a class called Light with a method of changeState, and then instantiating new Light objects for each element with a classname of light.

To demonstrate the power of custom events, we’re going to create a simple tool for searching Twitter. The tool will offer several ways for a user to add search terms to the display: by entering a search term in a text box, by entering multiple search terms in the URL, and by querying Twitter for trending terms.

The results for each term will be shown in a results container; these containers will be able to be expanded, collapsed, refreshed, and removed, either individually or all at once.

When we’re done, it will look like this:


We’ll start with some basic HTML:

<h1>Twitter Search</h1>
<input type="button" id="get_trends" 
    value="Load Trending Terms" />
 
<form>
    <input type="text" class="input_text" 
        id="search_term" />
    <input type="submit" class="input_submit" 
        value="Add Search Term" />
</form>
 
<div id="twitter">
    <div class="template results">
        <h2>Search Results for 
        <span class="search_term"></span></h2>
    </div>
</div>

This gives us a container (#twitter) for our widget, a template for our results containers (hidden via CSS), and a simple form where users can input a search term. (For the sake of simplicity, we’re going to assume that our application is JavaScript-only and that our users will always have CSS.)

There are two types of objects we’ll want to act on: the results containers, and the Twitter container.

The results containers are the heart of the application. We’ll create a plugin that will prepare each results container once it’s added to the Twitter container. Among other things, it will bind the custom events for each container and add the action buttons at the top right of each container. Each results container will have the following custom events:

refresh

Mark the container as being in the “refreshing” state, and fire the request to fetch the data for the search term.

populate

Receive the returned JSON data and use it to populate the container.

remove

Remove the container from the page after the user verifies the request to do so. Verification can be bypassed by passing true as the second argument to the event handler. The remove event also removes the term associated with the results container from the global object containing the search terms.

collapse

Add a class of collapsed to the container, which will hide the results via CSS. It will also turn the container’s “Collapse” button into an “Expand” button.

expand

Remove the collapsed class from the container. It will also turn the container’s “Expand” button into a “Collapse” button.

The plugin is also responsible for adding the action buttons to the container. It binds a click event to each action’s list item, and uses the list item’s class to determine which custom event will be triggered on the corresponding results container.

$.fn.twitterResult = function(settings) {
    return this.each(function() {
        var $results = $(this),
            $actions = $.fn.twitterResult.actions = 
                $.fn.twitterResult.actions || 
                $.fn.twitterResult.createActions(),
            $a = $actions.clone().prependTo($results),
            term = settings.term;

        $results.find('span.search_term').text(term);

        $.each(
            ['refresh', 'populate', 'remove', 'collapse', 'expand'], 
            function(i, ev) { 
                $results.bind(
                    ev, 
                    { term : term },
                    $.fn.twitterResult.events[ev]
                ); 
            }
        );

        // use the class of each action to figure out 
        // which event it will trigger on the results panel
        $a.find('li').click(function() {
            // pass the li that was clicked to the function
            // so it can be manipulated if needed
            $results.trigger($(this).attr('class'), [ $(this) ]);
        });
    });
};

$.fn.twitterResult.createActions = function() {
    return $('<ul class="actions" />').append(
        '<li class="refresh">Refresh</li>' +
        '<li class="remove">Remove</li>' +
        '<li class="collapse">Collapse</li>'
    );
};

$.fn.twitterResult.events = {
    refresh : function(e) {
           // indicate that the results are refreshing
        var $this = $(this).addClass('refreshing');

        $this.find('p.tweet').remove();
        $results.append('<p class="loading">Loading ...</p>');

        // get the twitter data using jsonp
        $.getJSON(
            'http://search.twitter.com/search.json?q=' +                     
                escape(e.data.term) + '&rpp=5&callback=?', 
            function(json) { 
                $this.trigger('populate', [ json ]); 
            }
        );
    },

    populate : function(e, json) {
        var results = json.results;
        var $this = $(this);

        $this.find('p.loading').remove();

        $.each(results, function(i,result) {
            var tweet = '<p class="tweet">' + 
                '<a href="http://twitter.com/' + 
                result.from_user + 
                '">' +
                result.from_user + 
                '</a>: ' +
                result.text + 
                ' <span class="date">' + 
                result.created_at + 
                '</span>' +
            '</p>';
            $this.append(tweet);
        });

        // indicate that the results 
        // are done refreshing
        $this.removeClass('refreshing');
    },

    remove : function(e, force) {
        if (
            !force &&     
            !confirm('Remove panel for term ' + e.data.term + '?')
        ) {
            return;
        }
        $(this).remove();

        // indicate that we no longer 
        // have a panel for the term
        search_terms[e.data.term] = 0;
    },

    collapse : function(e) {
        $(this).find('li.collapse').removeClass('collapse')
            .addClass('expand').text('Expand');

        $(this).addClass('collapsed');
    },

    expand : function(e) {
        $(this).find('li.expand').removeClass('expand')
            .addClass('collapse').text('Collapse');

        $(this).removeClass('collapsed');
    }
};

The Twitter container itself will have just two custom events:

getResults

Receives a search term and checks to determine whether there’s already a results container for the term; if not, adds a results container using the results template, set up the results container using the $.fn.twitterResult plugin discussed above, and then triggers the refresh event on the results container in order to actually load the results. Finally, it will store the search term so the application knows not to re-fetch the term.

getTrends

Queries Twitter for the top 10 trending terms, then iterates over them and triggers the getResults event for each of them, thereby adding a results container for each term.

Here's how the Twitter container bindings look:

$('#twitter')
    .bind('getResults', function(e, term) {
        // make sure we don't have a box for this term already
        if (!search_terms[term]) { 
            var $this = $(this);
            var $template = $this.find('div.template');

            // make a copy of the template div
            // and insert it as the first results box
            $results = $template.clone().
                removeClass('template').
                insertBefore($this.find('div:first')).
                twitterResult({
                    'term' : term
                });

            // load the content using the "refresh" 
            // custom event that we bound to the results container
            $results.trigger('refresh');
            search_terms[term] = 1;
        }
    })
    .bind('getTrends', function(e) {
        var $this = $(this);
        $.getJSON('http://search.twitter.com/trends.json?callback=?', function(json) {
                var trends = json.trends; 
                $.each(trends, function(i, trend) {
                    $this.trigger('getResults', [ trend.name ]);
                });
            });
    });

So far, we’ve written a lot of code that does approximately nothing, but that’s OK. By specifying all the behaviors that we want our core objects to have, we’ve created a solid framework for rapidly building out the interface.

Let’s start by hooking up our text input and the “Load Trending Terms” button. For the text input, we’ll capture the term that was entered in the input and pass it as we trigger the Twitter container’s getResults event. Clicking the “Load Trending Terms” will trigger the Twitter container’s getTrends event:

$('form').submit(function(e) {
    e.preventDefault();
    var term = $('#search_term').val();
    $('#twitter').trigger('getResults', [ term ]);
});

$('#get_trends').click(function() {
    $('#twitter').trigger('getTrends'); 
});

By adding a few buttons with the appropriate IDs, we can make it possible to remove, collapse, expand, and refresh all results containers at once, as shown below. For the remove button, note how we’re passing a value of true to the event handler as its second argument, telling the event handler that we don’t want to verify the removal of individual containers.

$.each(['refresh', 'expand', 'collapse'], function(i, ev) {
    $('#' + ev).click(function(e) { $('#twitter div.results').trigger(ev); });
});

$('#remove').click(function(e) {
    if (confirm('Remove all results?')) {
        $('#twitter div.results').trigger('remove', [ true ]);
    }
});