jQuery 使用 jQuery UI 部件工厂编写带状态的插件(翻译)

首先,我们要创建一个progress bar,它只允许我们简单的设置进度值。正如我们接下来将要看到的,我们需要通过调用 jQuery.widget 及其两个参数来实现这一操作,这两个参数分别是:将要创建的插件的名称,以及一个包含该插件所支持方法的对象字面量(object literal)。

当我们的插件被调用时,它将创建一个新的插件实例,并且包含在该实例上下文(context)中的方法都会被执行。这与标准的jQuery插件有两个主要的区别。首先,上下文 (context)是一个对象,而不是DOM元素。其次,上下文(context)始终都是一个单独的对象,而不是集合。

使用 jQuery UI 部件工厂创建一个简单的,带状态的插件:

1 $.widget( "nmk.progressbar", {
2 
3     _create: function() {
4         var progress = this.options.value + "%";
5         this.element.addClass( "progressbar" ).text( progress );
6     }
7 
8 });

 插件的名称必须包含一个命名空间(namespace);在我们的例子当中使用 nmk 作为命名空间(namespace)。关于命名空间(namespace)这里有一个限制,就是它只能够拥有一个层级;换句话说,我们不能使用类似 nmk.foo 这样的形式来作为命名空间(namespace)。同时我们看到部件工厂已经为我们提供了两个属性。第一个属性是 this.element ,它是一个只包含一个元素的jQuery对象。如果调用插件的jQuery对象包含多个元素,那么每一个元素将分别拥有一个插件实例,并且每一个插件实例都拥有自己的 this.element 属性。第二个属性是 this.options ,该属性是一个散列,包含有与我们插件选项相对应的所有键/值配对。

将选项传递给部件:

1 $( "<div />" ).appendTo( "body" ).progressbar({ value: 20 });

当我们调用 jQuery.widget 时,它通过向 jQuery.fn 添加一个方法来扩展jQuery(也是我们创建标准插件的方式)。被添加的方法的名称基于你在调用 jQuery.widget 时所使用的名称,不包含命名空间(namespace)。在我们的例子当中,它将创建 jQuery.fn.progressbar。传递给插件的选项在插件实例内部已经在 this.options 中进行了设置。

正如接下来我们将要展示的,我们可以为任何选项指定一个默认值。当设计API时,你应当想好该插件最常见的使用情形,以便可以设置合适的默认值,并使所有的选项都是完全可选的。

为部件设置默认选项值:

 1 $.widget( "nmk.progressbar", {
 2 
 3     // Default options.
 4     options: {
 5         value: 0
 6     },
 7 
 8     _create: function() {
 9         var progress = this.options.value + "%";
10         this.element.addClass( "progressbar" ).text( progress );
11     }
12 
13 });

为部件添加方法

现在我们可以初始化progress bar了,通过调用插件实例中的方法我们使其能够执行一些操作。要在插件中定义方法,我们只需要在传递给 jQuery.widget 的对象字面量(object literal)当中包含该方法。同时我们可以在方法名前面添加下划线(_)来使其成为私有方法。

创建部件方法:

 1 $.widget( "nmk.progressbar", {
 2     options: {
 3         value: 0
 4     },
 5 
 6     _create: function() {
 7         var progress = this.options.value + "%";
 8         this.element.addClass("progressbar").text( progress );
 9     },
10 
11     // Create a public method.
12     value: function( value ) {
13 
14         // No value passed, act as a getter.
15         if ( value === undefined ) {
16 
17             return this.options.value;
18 
19         // Value passed, act as a setter.
20         } else {
21 
22             this.options.value = this._constrain( value );
23             var progress = this.options.value + "%";
24             this.element.text( progress );
25 
26         }
27 
28     },
29 
30     // Create a private method.
31     _constrain: function( value ) {
32 
33         if ( value > 100 ) {
34             value = 100;
35         }
36 
37         if ( value < 0 ) {
38             value = 0;
39         }
40 
41         return value;
42     }
43 
44 });

 要在插件实例上调用方法,只需要将方法的名称传递给jQuery插件。如果所调用的是带参数的方法,只需将这些参数放置在方法名的后面。

在插件实例上调用方法:

 1 var bar = $( "<div />" ).appendTo( "body").progressbar({ value: 20 });
 2 
 3 // Get the current value.
 4 alert( bar.progressbar( "value" ) );
 5 
 6 // Update the value.
 7 bar.progressbar( "value", 50 );
 8 
 9 // Get the current value again.
10 alert( bar.progressbar( "value" ) );

操纵部件的选项
option方法是插件中内置的方法之一。该方法允许你在初始化之后对属性进行设置。该方法的工作方式与jQuery的 .css().attr() 方法相同:你可以通过向其传递名称来将它作为getter,传递名称和值来将它作为setter,传递一个键/值配对的散列来同时设置多个值。当它被作为getter时,插件将返回与传递进来的名称相对应的选项的当前值。当它被作为setter时,每一个已经设置的选项都会调用插件的 _setOption 方法。当选项发生改变时,我们可以在插件中指定 _setOption 方法来进行反应。

当选项被设置时作出响应:

 1 $.widget( "nmk.progressbar", {
 2 
 3     options: {
 4         value: 0
 5     },
 6 
 7     _create: function() {
 8         this.element.addClass( "progressbar" );
 9         this._update();
10     },
11 
12     _setOption: function( key, value ) {
13         this.options[ key ] = value;
14         this._update();
15     },
16 
17     _update: function() {
18         var progress = this.options.value + "%";
19         this.element.text( progress );
20     }
21 
22 });

添加回调函数

添加回调函数让用户可以在插件状态发生变化时作出反应是扩展插件的一种简单的方式。下面我们将看到如何为progress bar添加回调函数,来表明进度何时到达百分之百。 _trigger 方法带有三个参数:回调函数的名称,原生事件对象用于触发回调函数,以及与事件有关的数据的散列。回调函数的名称是唯一必要的参数,而其他的参数在用户希望在插件上实现自己的方法时将十分有用。例如,我们编写了一个可拖动的插件,当触发拖动回调函数时,我们可以传递一个原生的鼠标移动事件,用户将可以通过这一事件对象,在 x/y 坐标的基础上对拖动作出反应。

为用户扩展提供回调函数:

 1 $.widget( "nmk.progressbar", {
 2 
 3     options: {
 4         value: 0
 5     },
 6 
 7     _create: function() {
 8         this.element.addClass( "progressbar" );
 9         this._update();
10     },
11 
12     _setOption: function( key, value ) {
13         this.options[ key ] = value;
14         this._update();
15     },
16 
17     _update: function() {
18         var progress = this.options.value + "%";
19         this.element.text( progress );
20         if ( this.options.value == 100 ) {
21             this._trigger( "complete", null, { value: 100 } );
22         }
23     }
24 
25 });

回调函数其实就是额外的选项,因此你可以像对待其它选项那样对其进行设置。每当回调函数被执行时,一个与之对应的事件也会被触发。事件的名称由插件的名称及回调函数的名称连接而成。回调函数和事件同时接收两个相同的参数:事件对象和与时间有关的数据的散列,我们将在之后看到相关的演示。

如果你希望插件的某项功能可以被用户阻止,最好的做法就是创建一个可取消的回调函数。用户可以像取消原生事件那样来取消回调函数,或者与之相关的事件:通过调用 event.preventDefault() 或者使用 return false 。如果用户取消了回调函数, _trigger 方法将返回 false 使得你可以在插件中实现合适的功能。

绑定部件事件:

 1 var bar = $( "<div />" ).appendTo( "body" ).progressbar({
 2 
 3     complete: function( event, data ) {
 4         alert( "Callbacks are great!" );
 5     }
 6 
 7 }).bind( "progressbarcomplete", function( event, data ) {
 8 
 9      alert( "Events bubble and support many handlers for extreme flexibility." );
10 
11      alert( "The progress bar value is " + data.value );
12 
13 });
14 
15 bar.progressbar( "option", "value", 100 );

部件工厂:深入(Under the Hood)
当你调用 jQuery.widget 时,它会为你的插件创建一个构造函数(constructor),并将你传递进来的对象字面量(object literal)作为对象实例的原型(prototype)。所有自动添加到插件中的功能都来自于一个基本的部件原型(widget prototype),它被定义为 jQuery.Widget.prototype。当插件实例被创建之后,它通过 jQuery.data 将插件存储在原始的DOM元素当中,并使用插件的名称来作为键值。

由于插件实例直接与DOM元素产生联系,因此如果你愿意的话,可以直接访问插件实例而非通过插件所暴露的方法。这将允许你直接调用插件实例上的方法,而非通过将方法的名称作为字符串传递给插件,此外你还将可以直接访问插件的属性。

 1 var bar = $( "<div />")
 2     .appendTo( "body" )
 3     .progressbar()
 4     .data( "progressbar" );
 5 
 6 // Call a method directly on the plugin instance.
 7 bar.option( "value", 50 );
 8 
 9 // Access properties on the plugin instance.
10 alert( bar.options.value );

插件的构造函数(constructor)和原型(prototype)所拥有的巨大好处之一就是使插件更易扩展。通过在插件原型(prototype)上添加或修改方法,我们可以改变插件所有实例的行为。例如,如果我们想要在progress bar中添加一个方法,用于重置进度为零。我们可以在插件的原型上添加这一方法,并且它立刻就可以被任意插件的实例所调用。

1 $.nmk.progressbar.prototype.reset = function() {
2     this._setOption( "value", 0 );
3 };

清理

在某些情况下,允许用户随时应用和取消应用插件将十分有用。你可以通过使用 destroy 方法来达成这一目的。在 destroy 方法当中,你应当撤销插件在初始化时或之后的使用中所进行的一切动作。 destroy 方法将在被插件所关联的元素从DOM当中被移除时,自动被调用,因此这同样可以用来进行垃圾回收。默认情况下, destroy 方法会移除DOM元素和插件实例间的联系, 因此在插件的 destroy 方法中调用基类的方法十分的重要。

为部件添加 destroy 方法:

 1 $.widget( "nmk.progressbar", {
 2 
 3     options: {
 4         value: 0
 5     },
 6 
 7     _create: function() {
 8         this.element.addClass("progressbar");
 9         this._update();
10     },
11 
12     _setOption: function( key, value ) {
13         this.options[ key ] = value;
14         this._update();
15     },
16 
17     _update: function() {
18         var progress = this.options.value + "%";
19         this.element.text( progress );
20         if ( this.options.value === 100 ) {
21             this._trigger( "complete", null, { value: 100 } );
22         }
23     },
24 
25     destroy: function() {
26         this.element
27             .removeClass( "progressbar" )
28             .text( "" );
29 
30         // Call the base destroy function.
31         $.Widget.prototype.destroy.call( this );
32     }
33 
34 });

总结
部件工厂是创建带状态插件的唯一方法。有少数一些不同的模型可以被使用,并且它们各有千秋。部件工厂为你解决了大部分常见的问题并且极大的提升了工作效率,它同时提升了代码的复用率,使其和其它带状态的插件一样的更加适用于jQuery UI。

原文地址:http://learn.jquery.com/plugins/stateful-plugins-with-widget-factory/