原文链接: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 );
}
});
$( "<div></div>" )
.appendTo( "body" )
.progressbar({ value: 20 });
$.widget( "custom.progressbar", {
// Default options.
options: {
value: 0
},
_create: function() {
var progress = this.options.value + "%";
this.element
.addClass( "progressbar" )
.text( progress );
}
});
现在我们可以初始化我们的进度条了,我们将通过调用插件实例上的方法来添加插件的功能。为了定义一个插件方法,我们只需要在传入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命名空间。在本篇文章的后面我们将介绍一种更自然的方式。
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" ) );
在我们的插件中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;
}
});
使你的插件具有扩展性的最简单方法之一是添加回调函数,这样用户就可以在插件的状态发生变化时做出响应。下面我们就来看看怎样通过添加回调函数的方法来实现在进度条达到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;
}
});
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 );
现在我们已经知道了怎么通过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 );
var bar = $.custom.progressbar( {}, $( "<div></div>" ).appendTo( "body") );
// Same result as before.
alert( bar.options.value );
使得插件拥有构造函数和原型的最大好处之一是容易扩展。通过向插件原型上添加或修改方法,我们可以修改插件所有实例的行为。例如,如果我们想要给我的进度条插件添加一个置零方法可以把它添加到原型上,就可以在所有实例上直接调用它了。
$.custom.progressbar.prototype.reset = function() {
this._setOption( "value", 0 );
};
在某些情况下,我们想允许用户可以随时应用和取消我们的插件。你可以通过_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( "" );
}
});
Widget Factory只是创建有状态插件的一种方法。还有其他一些模型,他们都各有自己的优势和劣势。Widget Factory给你解决了很多常见的问题,可以大大提高生产率、也提高了代码的重用性、就像其他大量有状态插件一样使得您的插件也更接近jQuery UI。
你可能已经注意到了在本篇文章中我们使用了custom命名可能感觉。Ui命名空间已经被官方jQuery UI 占用了。当你创建你的插件的时候你应该使用你自己的命名空间。这将使得插件更清晰表面它的来源、是否属于一个更大的插件群体。