原文链接:http://learn.jquery.com/jquery-ui/widget-factory/how-to-use-the-widget-factory/
刚开始我们将创建一个只能设置一次的进度条插件。正像我们下面看到的,可以通过调用jQuery.widget来完成操作,它有两个参数:一个是插件的名字,另一个是对象字面量包含了创建插件的函数。当我们的插件被调用时,它将创建一个插件实例,所有的函数都会在实例上下文中执行。这和标准的jQuery插件有两方面的不同。一方面是上下文是一个对象,不是一个DOM元素;另一方面是上下文始终是一个单一对象,永远不会是一个集合。
$.widget( "custom.progressbar", { _create: function() { var progress = this.options.value + "%"; this.element .addClass( "progressbar" ) .text( progress ); } });
插件的名字必须包含一个命名空间,在这个案例中我们使用了custom命名空间。在当前命名空间有所限制的是我们必须使用一个命名空间。我们也可以看到Widget Factory为我们提供了两个属性。this.element 是一个只包含一个DOM元素的jQuery对象。如果我们的插件在包含了多个元素的jQuery对象上调用,将会对每个元素创建插件实例,并且每个实例都拥有自己的this.element。第二个属性是this.options,它是一个包含键/值对的散列用于我们所有插件的配置。可以像下面这样为我们的插件传入配置。
$( "<div></div>" ) .appendTo( "body" ) .progressbar({ value: 20 });
当我们调用jQuery.widget的时候将会向jQuery.fn添加一个函数来扩展jQuery(这是创建标准插件的方法)。添加的函数的名字取决于你传入jQuery.widget的名字,名字不包含命名空间,在这个案例中应该是“progressbar”。就像下面的示例一样,我们可以给设置任何的默认配置项。当你设计API时,应该考虑到插件的大部分使用情景,这样你就可以设置大部分的默认值并且使得所有的配置都是可选的。
$.widget( "custom.progressbar", { // Default options. options: { value: 0 }, _create: function() { var progress = this.options.value + "%"; this.element .addClass( "progressbar" ) .text( progress ); } });
Calling Plugin Methods
现在我们可以初始化我们的进度条了,我们将通过调用插件实例上的方法来添加插件的功能。为了定义一个插件方法,我们只需要在传入jQuery.widget的对象字面量参数中包含相应的函数即可。我们也可以通过给函数名加下滑线来定义私有函数。(这里的“函数”使用“方法”一词会更准确)
$.widget( "custom.progressbar", { options: { value: 0 }, _create: function() { var progress = this.options.value + "%"; this.element .addClass( "progressbar" ) .text( progress ); }, // Create a public method. value: function( value ) { // No value passed, act as a getter. if ( value === undefined ) { return this.options.value; } // Value passed, act as a setter. this.options.value = this._constrain( value ); var progress = this.options.value + "%"; this.element.text( progress ); }, // Create a private method. _constrain: function( value ) { if ( value > 100 ) { value = 100; } if ( value < 0 ) { value = 0; } return value; } });
想要在插件实例上调用一个方法,你需要把方法名传给jQuery插件。如过你调用的方法需要参数的话,你只需要在传递的方法名后面依次传入参数即可。
注意:通过将方法名直接添加的jQuery函数上的方式来执行方法可能会有异常。这样做是为了避免在保持链式调用能力时污染了jQuery命名空间。在本篇文章的后面我们将介绍一种更自然的方式。
var bar = $( "<div></div>" ) .appendTo( "body" ) .progressbar({ value: 20 }); // Get the current value. alert( bar.progressbar( "value" ) ); // Update the value. bar.progressbar( "value", 50 ); // Get the current value again. alert( bar.progressbar( "value" ) );
Working with Options
在我们的插件中option是自动可以使用的方法之一。Option方法使得你可以在初始化之后获取或者设置配置项。这个方法的效果就像jQuery的.css()和.attr()一样:你可以只传递一个名把它当作getter,也可以传递一个名和一个值把它当作一个单项setter,甚至是传递一个包含了多个名值对的哈希表(对象字面量)来依次设置多个值。当把它当作一个getter使用时,插件将会返回option的当前对于的名字的值。当把它当作setter来使用时,插件将会对每个名和值调用插件中的_setOption方法。我们可以在插件中重写_setOption方法来相应配置项的改变。为了单独对同时有多个属性改变这种情况进行响应,我们可以重写_setOptions。
$.widget( "custom.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass( "progressbar" ); this.refresh(); }, _setOption: function( key, value ) { if ( key === "value" ) { value = this._constrain( value ); } this._super( key, value ); }, _setOptions: function( options ) { this._super( options ); this.refresh(); }, refresh: function() { var progress = this.options.value + "%"; this.element.text( progress ); }, _constrain: function( value ) { if ( value > 100 ) { value = 100; } if ( value < 0 ) { value = 0; } return value; } });
Adding Callbacks
使你的插件具有扩展性的最简单方法之一是添加回调函数,这样用户就可以在插件的状态发生变化时做出响应。下面我们就来看看怎样通过添加回调函数的方法来实现在进度条达到100%时做出提示。_trgger方法需要三个参数:回调函数的名字,初始化回调函数的事件对象,还有一个是与事件相关的名值对数据。只有函数名是必须的,不过其他的参数对于想要在你的插件之上做更多个性化功能的用户来说是非常有用的。例如,如果我们做一个可拖拽插件,在触发拖拽回调函数时我们可以传入鼠标的移动事件对象。这样使得用户可以通过事件对象提供的坐标值来响应拖拽。注意,原本传入_trigger的必须是一个jQuery事件对象,而不是一个本地浏览器事件对象。
$.widget( "custom.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass( "progressbar" ); this.refresh(); }, _setOption: function( key, value ) { if ( key === "value" ) { value = this._constrain( value ); } this._super( key, value ); }, _setOptions: function( options ) { this._super( options ); this.refresh(); }, refresh: function() { var progress = this.options.value + "%"; this.element.text( progress ); if ( this.options.value == 100 ) { this._trigger( "complete", null, { value: 100 } ); } }, _constrain: function( value ) { if ( value > 100 ) { value = 100; } if ( value < 0 ) { value = 0; } return value; } });
回调函数本质上只是可选的配置,因此你可以像配置其它项一样配置回调函数。无论回调函数什么时候执行,一个相关的事件就会同时触发。事件的类型由插件名字和回调函数的名字连在一起确定。回调和事件都需要两个参数:一个事件对象和跟事件相关的一系列名值对数据,就像下面所看到的。你的插件可能想让可以关掉某些功能。最好的办法是提供取消回调函数。用户可以取消一个回调函数,或者是它相关的事件,就像他们可以通过调用event.preventDefault()或者返回false来取消任何本地事件一样。如果用户取消了回调函数,_trrgger方法将返回false,你就可以在插件里实现相应的相应。
var bar = $( "<div></div>" ) .appendTo( "body" ) .progressbar({ complete: function( event, data ) { alert( "Callbacks are great!" ); } }) .bind( "progressbarcomplete", function( event, data ) { alert( "Events bubble and support many handlers for extreme flexibility." ); alert( "The progress bar value is " + data.value ); }); bar.progressbar( "option", "value", 100 );
Looking Under the Hood
现在我们已经知道了怎么通过widget factory来创建一个插件,接下来我们来看看它是怎么工作的。当你调用jQuery.widget时,它为你的插件创建了一个构造方法,并且把你传入的对象字面量设置成了你插件所有实例的原型。所有自动添加到你插件中的功能都是来自于一个基础插件的原型,这个原型定义在jQuery.Widget.prototy中。当创建一个插件实例时,原型通过jQuery.data存储在一个原始DOM元素中,插件名字将作为键。(后面这两句不太明白,建议看原文)
因为插件实例直接关联到DOM元素,如果你喜欢的话你可以直接获得插件实例而不是通过暴露的插件方法。这将允许你在插件实例上直接调用方法而不是以字符串的形式传递方法名,并且这样也可以直接访问插件的属性。
var bar = $( "<div></div>" ) .appendTo( "body" ) .progressbar() .data( "progressbar" ); // Call a method directly on the plugin instance. bar.option( "value", 50 ); // Access properties on the plugin instance. alert( bar.options.value );
你也可以直接调用构造方法,并传递配置项和DOM元素作为参数来创建一个插件实例,这样就不需要使用插件方法了。
var bar = $.custom.progressbar( {}, $( "<div></div>" ).appendTo( "body") ); // Same result as before. alert( bar.options.value );
Extending a Plugin's Prototype
使得插件拥有构造函数和原型的最大好处之一是容易扩展。通过向插件原型上添加或修改方法,我们可以修改插件所有实例的行为。例如,如果我们想要给我的进度条插件添加一个置零方法可以把它添加到原型上,就可以在所有实例上直接调用它了。
$.custom.progressbar.prototype.reset = function() { this._setOption( "value", 0 ); };
Cleaning Up
在某些情况下,我们想允许用户可以随时应用和取消我们的插件。你可以通过_destroy方法来实现。在_destroy内部,你应该撤消所有初始化做的事情或者后来使用的东西。_destroy方法会在destroy方法中调用。而在你的插件所绑定的元素从DOM中移除是会自动调用destroy方法。所有这同时也可以用于垃圾回收。基类的.destry()方法也实现了一些通用的清理工作,像把关联的实例从widget DOM元素中移除,解除绑定在widget命名空间的事件,接触所有通过_bing()绑定的事件。
$.widget( "custom.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass( "progressbar" ); this.refresh(); }, _setOption: function( key, value ) { if ( key === "value" ) { value = this._constrain( value ); } this._super( key, value ); }, _setOptions: function( options ) { this._super( options ); this.refresh(); }, refresh: function() { var progress = this.options.value + "%"; this.element.text( progress ); if ( this.options.value == 100 ) { this._trigger( "complete", null, { value: 100 } ); } }, _constrain: function( value ) { if ( value > 100 ) { value = 100; } if ( value < 0 ) { value = 0; } return value; }, _destroy: function() { this.element .removeClass( "progressbar" ) .text( "" ); } });
Closing Comments
Widget Factory只是创建有状态插件的一种方法。还有其他一些模型,他们都各有自己的优势和劣势。Widget Factory给你解决了很多常见的问题,可以大大提高生产率、也提高了代码的重用性、就像其他大量有状态插件一样使得您的插件也更接近jQuery UI。
你可能已经注意到了在本篇文章中我们使用了custom命名可能感觉。Ui命名空间已经被官方jQuery UI 占用了。当你创建你的插件的时候你应该使用你自己的命名空间。这将使得插件更清晰表面它的来源、是否属于一个更大的插件群体。