jQuery UI の プラグイン定義関数 $.widget を使ってみる

jQuery UI の tabs や accordion といったウィジェットプラグインでは、専用のプラグイン定義関数 $.widgetプラグイン定義することで、一環性のあるプラグイン API を利用者に提供しています。

今回は、$.widget を使ったプラグインの定義方法について調べてみました。

ui.core.js の入手

$.widgetjQuery UI の ui.core.js というソースファイル内に定義されてます。

公式サイト(http://jqueryui.com/download)よりプラグイン一式をダウンロードし、zip ファイルを解凍すると、以下パスに ui.core.js があるので、これを読み込み $.widget を使用できるようにします。

jquery-ui-1.7.2.custom/development-bundle/ui/ui.core.js

$.widget によるプラグインの定義

//プラグインの定義
$.widget("my.plugin", {
    _init: function(){
        //初期化処理
    }
});
$.extend($.my.plugin, {
    defaults: {
        width : 200,
        height : 100
    }
});

//実行
$('#target1').plugin({
    widht:300
});

$.widget の第一引数には名前空間付きプラグイン名を指定します。例では my が名前空間、plugin がプラグイン名になります。第二引数にはメソッドを記述します。_init という名前で定義するとプラグインメソッドの初期化処理として実行されます。

$.widget 関数を実行すると、指定プラグイン名の関数オブジェクトが jQuery セレクタ配下に定義されます($.my.plugin)。これに対し defaults というプロパティ名で、初期化パラメータのデフォルト値を記述します。通常、自前でプラグインを書いた場合は、$.extend を使用し、デフォルト値を上書きした値を内部パラメータとして保持する記述をしますが、$.widgetプラグイン定義を行うと、この記述を省くことができます。
パラメータは this.options で参照できます。

_init: function(){
    this.options.width  //300
    this.options.height //100
}

jQuery セレクタで取得した処理対象となっている要素は this.element で参照できます。

_init: function(){
    this.element // $('#target1')
}

jQuery セレクタにより複数要素選択されてた場合は、1つ1つの要素に対して _init() が実行され this.element はカレント要素のみがセットされます。

$('#target1,#target2').plugin();
...
_init: function(){  // 2 回実行される
    this.element    // 1 回目は $('#target1') , 2 回目は $('#target2')
}

ちなみに this は new 演算子により $.my.plugin をインスタンス化したもの(以降プラグインインスタンスと記述)で、各要素は個別に専用のプラグインインスタンスを保持することになります。

その他にも以下のようなプロパティが参照可能です。

this.namespace
名前空間名が参照できます。例では "my" となります。
this.widgetName
メソッド名が参照でいます。例では "plugin" となります。
this.widgetBaseClass
内部的に使用することのできるベースクラス名が参照できます。例では "my-plugin" となります。

メソッドの定義と実行

_init 以外にもプラグインとして必要なメソッドを定義することができます。

$.widget("my.plugin", {
    _init: function(){ //... },
    _hoge: function(){ //... },
    hoge: function(){ //... },
    fuga: function(){ //... }
});

メソッド名を _(ハイフン)から始まる名前にするとプライベートメソッド、_(ハイフン)無しだとパブリックメソッドとして定義されます。
プラグインの内部からこれらメソッドを実行するには、this.メソッド名() と記述します。各メソッドにおける値の返却処理( return 文 )の記述の有無に関係なく、メソッドの返却値は常にプラグインインスタンスとなるので、メソッドチェーンな記述が可能です。

_init: function(){
    this._hoge().hoge();
},

パブリックメソッドは、プラグイン利用者側から呼び出すことができます。プラグインメソッドによる初期化後、第一引数にパブリックメソッド名、第二引数以降にそのメソッドに必要な引数を指定したかたちで、再度、同一要素に対しプラグインメソッドを実行すると呼び出すことができます。

$('#target1').plugin();
$('button').click(function(){
    $('#target1').plugin( 'hoge' , 111 , 222 , 333 );
});

jQuery の標準メソッドと同様 jQuery セレクタで複数要素取得した状態でパブリックメソッドを実行した場合、選択された全ての要素に対し処理が適用されます。返却値は jQuery オブジェクトになるので メソッドチェーンな記述が可能です。

//#target1,#target2 に対し hoge / fuga メソッドが実行される
$('#target1,#target2').plugin('hoge').plugin('fuga');
ゲッターメソッドの定義

ゲッターメソッドを記述する場合はそのメソッドがゲッターメソッドであることを宣言する必要あります。$.my.plugin 配下の getter プロパティにゲッターメソッド名を文字列で記述します。ゲッターメソッドが複数ある場合は空白で区切ります。

$.widget("my.plugin", {
    //...
    getHoge: function(){
        return HOGE;
    },
    getFuga: function(){
        return FUGA;
    },
});

$.extend($.my.plugin, {
    getter : 'getHoge getFuga', //ゲッターメソッド名
    //...
});

jQuery のゲッターメソッドと同様、複数要素選択された状態でゲッターメソッドを実行すると、先頭の要素が保持するプラグインインスタンスのゲッターメソッドが実行されます。

//#target1 の getHoge が実行される
$('#target1,#target2').plugin('getHoge');
パラメータの取得と変更

プラグイン実行時に指定したパラメータの取得と変更が可能です。プラグインメソッドの第一引数に "option" 、第二引数にパラメータ名を指定すると取得できます。

$('#target1').plugin();
$('#target1').plugin('option','height'); // 100

第三引数に値を指定することで、パラメータの値を変更できます。

$('#target1')
    .plugin('option','height',500) 
    .plugin('option','height')  // 500
disable / enable メソッド

disable メソッドを実行するとプラグインの実行対象となった要素に、"名前空間"-"プラグイン名"-disabled、"名前空間"-state-disabled というクラス名を割り当てることができます。enable メソッドでは、これらのクラス名の割り当てを解除することができます。

//#target1 のクラスに my-plugin-disabled my-state-disabled が割り当てられる
$('#target1').plugin('disable');

//my-plugin-disabled my-state-disabled の割り当て解除
$('#target1').plugin('enable');
destroy メソッド

プラグインメソッドが実行されると内部生成されたプラグインインスタンスが、実行対象となった各要素に data() メソッドにより保持されます。destroy メソッドを使用するとプラグインインスタンスの保持が解除され、前述のプラグイン API が使用できなくなります。

$('#target1').plugin('option','height'); //100
$('#target1').plugin('destroy').plugin('option','height'); //undefined
プラグインインスタンスの取得

基本、プラグイン API の実行は、プラグインメソッドを経由して行われますが、data() メソッドでプラグインインスタンスを取得することで、直接実行することもできます。

var api = $('#target1').data('plugin');
api.hoge().fuga();

$.widget を使ってプラグインを書いてみる

前回のエントリ「テーブルのヘッダとフッタを固定する簡易プラグイン」で作成したプラグインを $.widget を使って書き直してみます。

プラグイン定義

(function($){
    $.widget("ex.exTable", {
        _init: function(){
            var o = this,c = o.options;
    
            //新設するウィジェットの外枠を生成
            c.container = $('<div class="ex-table-container">' +
                '<div class="ex-table-head"><table/></div>' +
                '<div class="ex-table-body"></div>' +
                '<div class="ex-table-foot"><table/></div>' +
                '</div>');
    
            //ウィジェットのヘッダ、ボディ、フッタ枠の取得と幅調整
            c.head = c.container.find('> div.ex-table-head').css({
                'padding-right':c.scrollbarWidth
            });
            c.foot = c.container.find('> div.ex-table-foot').css({
                'padding-right':c.scrollbarWidth
            });
            c.body = c.container.find('> div.ex-table-body').css({
                'overflow-x' : 'hidden',
                'overflow-y' : 'scroll',
                height : c.height
            });
    
            //処理対象テーブルの thead / tbody / tfoot の取得
            var thead = o.element.find('> thead')
                ,tfoot = o.element.find('> tfoot')
                ,tbody = o.element.find('> tbody');
    
            //幅を固定する
            o._fixedWidth(thead);
            o._fixedWidth(tfoot);
            o._fixedWidth(tbody);
            c.container.width(o.element.width()+c.scrollbarWidth);
    
            //新設するウィジェットの外枠をページに挿入
            o.element.after(c.container);
    
            //ウィジェットのヘッダ、フッタ枠に thead と tfoot を挿入
            o.element.find('> thead').appendTo(c.head.find('> table'));
            o.element.find('> tfoot').appendTo(c.foot.find('> table'));
            o.element.appendTo(c.body);

            //スクロールイベントの割り当て          
            c.body.scroll(function(){
                o.element.trigger('exTable_scroll');
            });

        },
        _fixedWidth : function(stack){
            var cols = stack.find('> tr:eq(0) > *');
            cols.each(function( idx ){
                var col = cols.eq(idx);
                col.width(col.width());
            });
        },
            
        //CSS セッターメソッド
        setContainerCSS : function(css){
            this.options.container.css(css);
        },
        setBodyCSS : function(css){
            this.options.body.css(css);
        },
        setCellsCSS : function(selector,css){
            this.options.container.find(selector || 'th,td').css(css);
        },
    
        //内部生成要素の ゲッターメソッド
        getContainer : function(){
            return this.options.container;
        },
        getHead : function(){
            return this.options.head;        
        },
        getBody : function(){
            return this.options.body;        
        },
        getFoot : function(){
            return this.options.foot;        
        }
    });
    $.extend($.ex.exTable, {
        version: '0.1.0',
        getter: 'getContainer getHead getBody getFoot',
        defaults: {
            scrollbarWidth :16,
            height : 200
        }
    });
})(jQuery);

プラグイン API の数が寂しかったので、内部生成した要素に CSS を適用するメソッド(setContainerCSS / setBodyCSS / setCellsCSS)を追加しました。これら要素に対するゲッターメソッドも一応定義してありますが、生成した要素は this.options に格納してるので、option メソッドで取得することも可能です。
またデータの表示領域がスクロールした際に発生するカスタムイベント(exTable_scroll)も追加定義してます。

実行

var targets = $('#target1,#target2')

    //プラグイン実行
    .exTable()

    //#target1,#target2にメソッドを適用
    .exTable('setContainerCSS',{
        background : '#505050',
        color : '#c0c0c0',
        border : 'solid 1px #202020'
    })
    .exTable('setCellsCSS','td',{
        background : '#4a4a4a'
    })

    //#target1にのみメソッドを適用
    .eq(0)
        .exTable('setCellsCSS','th,td',{
            color : '#aaccff'
        })
    .end()

    //#target2にのみメソッドを適用
    .eq(1)
        .exTable('setCellsCSS','th,td',{
            color : '#ffaacc'
        })
    .end()

    //#target1,#target2のスクロール位置の同期
    .bind('exTable_scroll',function(){
        $('#target' + (2 - targets.index(this)))
            .exTable('getBody').scrollTop(
                $(this).exTable('getBody').scrollTop()
            );
    });

Demo

2つのテーブルに対しプラグインを適用した後、メソッドチェーンでつないで以下処理を行っています。

  • プラグイン API を使用してテーブル1、2に共通の CSS を適用
  • eq() メソッドと end() メソッドで処理対象要素を切り替え、テーブル1と2で異なる CSS を適用
  • bind() メソッドによるカスタムイベントの割り当てで、テーブル1、2のスクロール位置の同期処理を適用

サンプルでは強引にメソッドチェーンを使ってる感がありますが、$.widget が生成するプラグインの実装が、たとえプラグイン API を使用した場合でも「jQuery メソッドの返却値は jQuery オブジェクト」という原則を厳守してればこそできる記述かと思います。