虽然大多数现有 jQuery 插件都是无状态的——也就是说,我们在一个元素上调用它们,这就是我们与插件交互的全部内容——但有一大类功能不符合基本的插件模式。
为了弥补这一空白,jQuery UI 实现了一个更高级的插件系统。新系统管理状态,允许通过单个插件暴露多个功能,并提供各种扩展点。该系统被称为 Widget 工厂,作为 jQuery UI 1.8 的一部分,它以 jQuery.widget 的形式暴露;但是,它可以独立于 jQuery UI 使用。
为了演示 Widget 工厂的功能,我们将构建一个简单的进度条插件。
首先,我们将创建一个进度条,它只允许我们设置一次进度。如下所示,这是通过调用 jQuery.widget() 并传入两个参数来完成的:要创建的插件名称,以及一个包含支持我们插件的函数的对象字面量。
当我们的插件被调用时,它将创建一个新的插件实例,所有函数都将在该实例的上下文中执行。这与标准 jQuery 插件在两个重要方面有所不同。首先,上下文是一个对象,而不是一个 DOM 元素。其次,上下文始终是单个对象,而不是集合。
使用 jQuery UI Widget 工厂的简单有状态插件
|
1
2
3
4
5
6
7
8
|
|
插件的名称必须包含一个命名空间,在本例中我们使用了 custom 命名空间。您只能创建一级深的命名空间,因此 custom.progressbar 是一个有效的插件名称,而 very.custom.progressbar 则不是。
注意:在我们的示例中,我们使用 custom 命名空间。ui 命名空间保留用于官方 jQuery UI 插件。在构建自己的插件时,您应该创建自己的命名空间。这可以清楚地表明插件的来源以及它是否属于更大的集合。
我们还可以看到 widget 工厂为我们提供了两个属性。this.element 是一个只包含一个元素的 jQuery 对象。如果我们的插件是在包含多个元素的 jQuery 对象上调用的,那么将为每个元素创建一个单独的插件实例,并且每个实例都有自己的 this.element。第二个属性 this.options 是一个哈希表,包含我们插件所有选项的键/值对。这些选项可以按此所示传递给我们的插件。
向 widget 传递选项
|
1
2
3
|
|
当我们调用 jQuery.widget() 时,它通过向 jQuery.fn 添加一个方法来扩展 jQuery(与我们创建标准插件的方式相同)。它添加的函数名称基于您传递给 jQuery.widget() 的名称,不带命名空间——在我们的例子中,它将创建 jQuery.fn.progressbar。传递给我们插件的选项是设置在插件实例内部 this.options 中的值。
如下所示,我们可以为任何选项指定默认值。在设计 API 时,您应该找出插件最常见的用例,以便设置合适的默认值并使所有选项真正可选。
为 widget 设置默认选项
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
|
link 向 Widget 添加方法
现在我们可以初始化进度条了,我们将添加通过调用插件实例上的方法来执行操作的功能。要定义一个插件方法,我们只需将函数包含在我们传递给 jQuery.widget() 的对象字面量中。我们还可以通过在函数名称前加上下划线来定义“私有”方法。
创建 widget 方法
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
|
要调用插件实例上的方法,您将方法名称传递给 jQuery 插件。如果您正在调用接受参数的方法,只需在方法名称之后传递这些参数即可。
注意:通过将方法名称传递给用于初始化插件的相同 jQuery 函数来执行方法可能看起来很奇怪。这样做是为了防止污染 jQuery 命名空间,同时保持方法调用链的能力。在本文后面,我们将看到可能感觉更自然的替代用法。
调用插件实例上的方法
|
1
2
3
4
5
6
7
8
9
10
11
12
|
|
link 使用选项
自动提供给我们插件的方法之一是 option() 方法。option() 方法允许您在初始化后获取和设置选项。此方法与 jQuery 的 .css() 和 .attr() 方法完全相同:您可以只传递一个名称将其用作 getter,传递一个名称和一个值将其用作单个 setter,或者传递一个名称/值对的哈希表来设置多个值。当用作 getter 时,插件将返回与传入名称对应的选项的当前值。当用作 setter 时,插件的 _setOption 方法将为正在设置的每个选项调用。我们可以在插件中指定 _setOption 方法来响应选项更改。对于独立于选项更改数量执行的操作,我们可以覆盖 _setOptions()。
响应选项设置
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
|
link 添加回调
使插件可扩展的最简单方法之一是添加回调,以便用户可以在插件状态更改时做出反应。我们可以在下面看到如何向我们的进度条添加回调,以表示进度何时达到 100%。_trigger() 方法接受三个参数:回调的名称、启动回调的 jQuery 事件对象以及与事件相关的哈希数据。回调名称是唯一必需的参数,但对于希望在您的插件之上实现自定义功能的用户来说,其他参数可能非常有用。例如,如果我们正在构建一个可拖动插件,我们可以在触发拖动回调时传递 mousemove 事件;这将允许用户根据事件对象提供的 x/y 坐标对拖动做出反应。
请注意,传递给 _trigger() 的事件必须是 jQuery 事件,而不是原生浏览器事件。
提供用户扩展的回调
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
|
回调函数本质上只是附加选项,因此您可以像其他任何选项一样获取和设置它们。每当执行回调时,也会触发相应的事件。事件类型由插件名称和回调名称连接而成。回调和事件都接收相同的两个参数:一个事件对象和与事件相关的哈希数据,如下所示。
您的插件可能具有您希望允许用户阻止的功能。支持此功能的最佳方法是创建可取消的回调。用户可以通过调用 event.preventDefault() 或返回 false,以与取消任何事件相同的方式取消回调或其关联事件。如果用户取消回调,_trigger() 方法将返回 false,以便您可以在插件中实现相应的功能。
绑定到 widget 事件
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
|
link 深入了解内部机制
现在我们已经了解了如何使用 widget 工厂构建插件,接下来让我们看看它是如何实际工作的。
当您调用 jQuery.widget() 时,它会为您的插件创建一个构造函数,并将您传入的对象字面量设置为您的插件实例的原型。自动添加到您插件的所有功能都来自一个基本 widget 原型,该原型定义为 jQuery.Widget.prototype。当创建插件实例时,它使用 jQuery.data 存储在原始 DOM 元素上,以插件的完整名称(插件的命名空间,加上一个连字符,再加上插件的名称)作为键。
例如,jQuery UI 对话框 widget 使用的键是 "ui-dialog"。
由于插件实例直接链接到 DOM 元素,如果您愿意,可以直接访问插件实例,而无需通过暴露的插件方法。这将允许您直接在插件实例上调用方法,而不是将方法名称作为字符串传递,并且还将使您能够直接访问插件的属性。
|
1
2
3
4
5
6
7
8
9
10
|
|
您也可以通过直接调用构造函数,传入选项和元素参数来创建实例,而无需通过插件方法。
|
1
2
3
4
|
|
link 扩展插件的原型
为插件提供构造函数和原型最大的好处之一是易于扩展插件。通过添加或修改插件原型上的方法,我们可以修改插件所有实例的行为。例如,如果我们要为进度条添加一个方法以将进度重置为 0%,我们可以将此方法添加到原型中,它将立即可用于任何插件实例。
|
1
2
3
|
|
有关扩展 widget 的更多信息,包括如何在现有 widget 的基础上构建全新的 widget,请参阅 使用 Widget 工厂扩展 Widgets。
link 清理
在某些情况下,允许用户应用然后稍后取消应用您的插件是有意义的。您可以通过 _destroy() 方法来实现此目的。在 _destroy() 方法中,您应该撤消插件在初始化或后续使用期间可能执行的任何操作。_destroy() 由 destroy() 方法调用,如果您的插件实例所关联的元素从 DOM 中移除,destroy() 方法会自动调用,因此这也可以用于垃圾回收。基本的 destroy() 方法还处理一些通用的清理操作,例如从 widget 的 DOM 元素中移除实例引用,解绑 widget 命名空间中所有元素上的事件,以及解绑使用 _bind() 添加的所有事件。
向 widget 添加销毁方法
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
|
link 结束语
Widget 工厂只是创建有状态插件的一种方式。有几种不同的模型可以使用,每种模型都有其自身的优缺点。Widget 工厂为您解决了许多常见问题,可以大大提高生产力,它还大大提高了代码重用,使其非常适合 jQuery UI 以及许多其他有状态插件。