編寫jQueryUI插件(widget)


使用jQueryUI的widget來寫插件,相比於基本的jquery插件有一些好處:

* 方便實現繼承,代碼重用

* 默認是單例

* widget已經給你實現好的一些常用方法,例如destroy

帶來好處的同時也帶來了荊棘和陷阱,本文的目的就是梳理這些荊棘,標出哪里有陷阱。

 

基本知識:命名規范,public, private, this, this.element

如何開始寫一個widget呢?模板如下:

(function ($) {
    // utility functions (won’t be inherited)
    function foo() {}
    
    $.widget('命名空間.插件名', $.繼承插件的命名空間.插件名,{ /* snip */ });
})(jQuery);        

其中命名空間是可選的,要不要繼承別的widget也是可選的。大頭是后面snip的部分,這也是下文要講的。

一般來說工具函數寫在widget外面比較合適,但如果你想要這些工具函數被子類繼承,則需要寫在widget里面。

寫在widget里面的,就有public和private之分,規則是:

public方法首字符不是_

private方法首字符是_


當調用方法時,會先判斷是否以_開頭,如果是則不執行調用。

如果我非要在外面調用private方法,該怎么做?並非一點辦法也沒有:

var instance = $('<div>');
instance.mywidget('publicFunction'); // work
instance.mywidget('_privateFunction'); // silently fail
instance.data('mywidget')._privateFunction(); // work
$.mynamespace.mywidget.prototype._privateFunction(); // work
 

在widget內,this表示的是什么?我們在widget的一個public函數內用console.log(this)打出來瞧瞧:

image

日志顯示,this是一個$.widget.$.(anonymous function).(anonymous function)

this.element是變成widget的那個jQuery對象,如果要用jquery的方法,往往首先要取到jquery對象。

this.options是插件的選項,下文會詳解。

this.__proto__包含了插件中定義的所有public和private函數,以及繼承過來的方法。

這里簡單介紹一下__proto__:每個對象都會在其內部初始化一個屬性,就是__proto__,當我們訪問一個對象的屬性 時,如果這個對象內部不存在這個屬性,那么他就會去__proto__里找這個屬性,這個__proto__又會有自己的__proto__,於是就這樣 一直找下去,也就是我們平時所說的原型鏈的概念。

_create  _init    destroy

widget factory實現了一種單例模式,即不允許在同一個jQuery對象上多次實例化。

當調用$(XX).widgetName()進行初始化的時候,會執行以下代碼(源碼截取自jquery.ui.widget.js):

var instance = $.data( this, name ); // 從widget自身取出名字為name的數據
if ( instance ) {
    instance.option( options || {} )._init();  // 若該數據已經存在則只調用_init
} else {
    $.data( this, name, new object( options, this ) ); // 若數據還沒有則新建一個實例,並將實例保存
}

 

當調用$(XX).widgetName(‘destroy’)進行銷毀的時候,執行以下代碼(源碼截取自jquery.ui.widget.js):

this.element
    .unbind( "." + this.widgetName )
    .removeData( this.widgetName ); // 刪除在create時保存的數據 

有一個removeData的操作,那么下次調用$(XX).widgetName()就會重新實例化了。

 

需要注意的是,destroy方法在jquery.ui.widget.js中是有默認實現的,而_create和_init沒有實現。因此如果用自己的方法覆蓋destroy,不要忘記調用默認的

destory: function () {
    console.log('destory');

    // call the original destroy method since we overwrote it
    $.Widget.prototype.destroy.call(this);
}

 

以下示例代碼驗證_create和_init的區別以及destroy的作用:

var mw = $('#test').myWidget(); // _create  _init
mw = $('#test').myWidget(); // _init
mw.myWidget('destory');
mw = $('#test').myWidget(); // _create  _init

 

那么在_create和_init以及destroy里分別應該做什么:

_create: 生成HTML,事件綁定。

_init: 執行默認的初始化動作,例如把頁面變成初始狀態。

destory: 調用$.Widget.prototype.destroy.call(this),刪除HTML。

 

注意:綁定事件要注意給事件名加命名空間后綴:例如 .bind('mouseenter.mywidget', this._hover)

 

options

選項,在widget中的定義是options,而在調用時是option,注意定義的時候有s,調用的時候沒s。

定義:

option

s

: {
    field1: 'default',
    function1: function () {
        console.log('default option function1');
    }
},

調用:

$('#test').mywidget('option', 'field1', 2);

widget默認實現了兩個函數:_setOptions和_setOption,_setOptions的實現就是對每個要修改的option調用_setOption,也就是說真正修改的動作在_setOption里。因此,如果要重寫_setOption函數,則一定不要忘記寫

$.Widget.prototype._setOption.apply(this, arguments);

 

_setOptions和_setOption這倆函數什么時候被調用呢?用下面這個例子來說明。

例如有這樣的_setOption和_setOptions:

 
_setOption: function (key, value) {
    console.log('_setOption: key=%s  value=%s', key, value);
    $.Widget.prototype._setOption.apply(this, arguments);
},

_setOptions: function (options) {
    var key;

    console.group('_setOptions');
    for (key in options) {
        this._setOption(key, options[key]);
    }
    console.groupEnd();

    return this;
},

以及一個打印options值的函數printOptions:

printOptions: function () {
    console.group('options');
    console.log('field1: %s', this.options.field1);
    console.log('function1: %s', this.options.function1);
    console.groupEnd();
},

我們像下面這樣調用:

var instance = $('<div>');

// create widget with default options
console.group();

instance.mywidget(); 
instance.mywidget('printOptions');

console.groupEnd();

// create widget with specified options
instance.mywidget('destroy');
console.group();

var opts = {
    field1: 'specified',
    function1: function () {
        console.log('specified option function1');
    },
};
instance.mywidget(opts); 
instance.mywidget('printOptions');

console.log('-------------');
instance.mywidget(opts); 

console.groupEnd();

// modify options
console.group();

instance.mywidget('option', 'field1', 2);
instance.mywidget('printOptions');

console.groupEnd();
 

打出的日志如下:

image

日志分為三大塊。

第一塊是不使用options來初始化,可以看到直接使用定義里默認的options,是不調用_setOption的。

第二塊是使用options來初始化,這一塊做了兩個實驗(日志中用--------將兩塊分隔),第一個實驗是完全重建(_create, _init),從日志可以看到並沒有調用_setOption;第二個實驗只是重新初始化(_init),用的options都一樣,從日志可以看到它調用了_setOption,且在_init之前調用的。

第三塊不是初始化,而僅僅是修改option值,可以清楚看到調用了_setOption。

 

何時會調用_setOption的結論:

1. 像instance.mywidget('option', 'field1', 2); 這樣顯式設置option時。

2. 帶着options初始化時:

如果實例不存在,即需要調用_create,則不調用_setOption;

如果實例已存在,僅需要調用_init,則會在調用_init之前調用_setOption。

 

 

_trigger

注意這個_trigger是jQueryUI widget factory里的,和jQuery里$.fn命名空間下的trigger函數不是一個東西(后者不帶下划線)。

_trigger一般用來回調用戶傳入options的callback。

在插件內部調用_trigger(‘myEvent’)即相當於調用options里面的myEvent這個回調函數。

要改動options里的event handler應該怎么做呢?不要使用bind/unbind,而是去修改options:

// bind (overwrite, not add event handler)
mw.myWidget('option', 'myEvent', function (event, ui) {
    console.log('new implement');
});
// unbind
mw.myWidget('option', 'myEvent', null);

 

總結一下:

this._trigger(‘eventName’)是widget特有的,用於調用options里定義的callback。

this.element.trigger(‘eventName’)是jQuery的,可參考jQuery的事件的用法。(其中this.element是表示該插件的jQuery對象)

 

一個_trigger的樣例:

// 模板
this._trigger( "callbackName" , [eventObject], [uiObject] )

callbackNameThe name of the event you want to dispatcheventObject(Optional)An (mocked) event object. _trigger wraps this object and stores it in event.originalEventThe user receives an object with event.type == this.widgetEventPrefix + "eventname"uiObject(Optional)An object containing useful properties the user may need to access.Protip: Use a method like ._uito generate objects with a consistent schema. 

// 調用樣例
this._trigger( "hover", e /* e.type == "mouseenter" */, { hovered: $(e.target)});
// The user can subscribe using an init option
$("#elem").filterable( { hover: function(e,ui) { } } );
// Or with traditional event binding/delegation
$("#elem").bind( "filterablehover" , function(e,ui) { } );

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM