原文:http://bililite.com/blog/understanding-jquery-ui-widgets-a-tutorial/
由于翻译后不能使用HTML和SCRIPT所以演示部分就不能看到了,请参照原文。
写这篇文章的目的是为了让我理解并开发出我自己的Widgets,同时我也希望这篇文章能对其他人也有帮助。Widget让我说就是一种用户界面元素,类似于按钮或者像复杂的DatePicker(日期选择控件)。但这一概念对于jQuery,更确切的说是一个类。这个类的成员与一些HTML元素相关,比如Draggable和Sortable。当然不是所有Widget都使用$.widget,例如datePicker就没有使用。
插件:修改元素
让我们用一个带CSS类的P元素作为目标举个例子。
<p class="target">This is a paragraph</p>
我们把它变绿:
$(".target").css({"background":'green'});
然后我们需要将这一功能更通用化:
$.fn.green = function() {return this.css({background: 'green'})}
上面的代码是我们可以在任意的选中元素上产生效果,但他没有让我们保持和某个元素的关联。例如如果我们需要制作一个恢复背景的功能,哪么我们必须确定他的初始颜色。如果要设计一个Darker(),我们就必须知道元素现在是什么颜色。
保持插件状态
我们可以创建一个与某元素管理单的对象:
element.myobject = new Myobject({'target': element})
$.fn.green2 = function() {
return this.each(function(){
if (!this.green) this.green = new Green(this); // associate our state-keeping object with the element
this.green.setLevel(15);
});
};
$.fn.off = function() {
return this.each(function(){
if (this.green) this.green.setLevel(16);
delete this.green; // recover the memory
});
};
$.fn.darker = function() {
return this.each(function(){
if (this.green) this.green.setLevel(this.green.getLevel()-1);
});
};
$.fn.lighter = function() {
return this.each(function(){
if (this.green) this.green.setLevel(this.green.getLevel()+1);
});
};
function Green(target){
greenlevels = ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0','#fff'];
this.target = target; // associate the element with the object
this.level = 0;
this.getLevel = function() { return this.level; }
this.setLevel = function(x) {
this.level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));
this.target.css({background: greenlevels[this.level]});
}
};
但是上面这种方式使得$.fn命名空间中出现了很多函数。我们也可以在该命名空间内再创建一个命名空间,但一般的设计思路使用一个字符串来调用相应的函数,即我们可以使用类似element.green2("darker")来调用darker()方法:
$.fn.green2 = function(which){
return this.each(function(){
if (which === undefined){ // initial call
if (!this.green) this.green = new Green($(this)); // associate our state-keeping object with the element
this.green.setLevel(15);
}else if (which == 'off'){
if (this.green) this.green.setLevel(16);
delete this.green
}else if (which == 'darker'){
if (this.green) this.green.setLevel(this.green.getLevel()-1);
}else if (which == 'lighter'){
if (this.green) this.green.setLevel(this.green.getLevel()+1);
}
});
};
function Green(target){
greenlevels = ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff'];
this.target = target; // associate the element with the object
this.level = 0;
this.getLevel = function() { return this.level; }
this.setLevel = function(x) {
this.level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));
this.target.css({background: greenlevels[this.level]});
}
};
插件与对象关联所带来的问题
上面的方式能够方便的写出程序,但是也带来了循环引用和可能的内存漏洞。浏览器对于Javascript和Dom元素是使用不同的垃圾回收方式,上面的引用方式可能就会带来问题,使得回收不能正常进行。
我们同时也要注意,在我们开发插件是也要注意内存的回收。
jQuery用$.fn.data解决了这一问题:
$(element).data('myobject', new Myobject({'target': element}))
但是,这样做的同时又带来了其他的问题,我们需要做很多“额外”的工作,这些工作与程序逻辑毫无关系,重复而无用。所以重复实现这一功能,需要抽象出来减少工作。
解决问题:$.widget
这就是$.widget出现的原因。它使得插件中的Javascript类的单个实例与每个元素相关联,这样我们在与对象进行交互操作元素的时候就不会带来内存绣楼的问题。
同上面一样,你还是需要创建类的构造函数。与之不同的是,我们需要一个包含了相关方法的“原型”。同时也有一些变化,如:_init()函数在构造是调用,destroy()方法在析构是调用。这两个函数都是预先定义的,不过你可以覆盖。element和上面我们的target是与类相关的jQuery对象。
Widget中所有已“_”开头的方法都属预定义似有形函数,外部不能调用。
var Green3 = { _init: function() { this.setLevel(15); },greenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff'],level: 0,getLevel: function() { return this.level; },setLevel: function(x) { this.level = Math.floor(Math.min(this.greenlevels.length-1, Math.max(0,x)));this.element.css({background: this.greenlevels[this.level]}); },darker: function() { this.setLevel(this.getLevel()-1); },lighter: function() { this.setLevel(this.getLevel()+1); },off: function() {this.element.css({background: 'none'});this.destroy(); // use the predefined function }};
上述代码即就是整个程序逻辑,使用下面代码生成插件:
$.widget("ui.green3", Green3); // create the widget