jQueryプラグインをうまいことカプセル化する設計方法

 

“There are only two hard things in Computer Science: cache invalidation and naming things”

だそうです。そういう訳で、jQueryプラグインのメソッド名が他のプラグインと衝突するのを回避するための設計方法を列挙してみた。今のところ、一番下の方法が自分的にはしっくり来ている。

[2011.02.02追記]  2011年はsub()方式がおすすめです。

jquery.myplugの基本形。基本的に$.fn.extend()は使わない方針。

/**
 * jquery.myplug基本形
 *
 * SYNOPSIS
 *
 * $('#target').myplug();
 * $('#target').myplug({param1 : val1, param2 : val2});
 */
;(function($) {

    var default_params = {
        param1 : 'a',
        param2 : 'b'
    };

    $.fn.myplug = function(params) {

        params = $.extend(default_params, params, {});

        // do something

        return this;
    }
})(jQuery);

 

複数メソッドを提供したい。

/**
 * jquery.myplug マルチメソッド(悪い例)
 *
 * SYNOPSIS
 *
 * $('#target').myplugMethod1(params);
 * $('#target').myplugMethod2(params);
 */
;(function($) {
    $.fn.myplugMethod1 = function(params) {return this;}
    $.fn.myplugMethod2 = function(params) {return this;}
})(jQuery);

上記はどこでも推奨していない悪い例。myplugというプラグインが$.fn配下にmyplugMethod1とmyplugMethod2という2つの名前を定義しているので、プラグイン間で衝突する可能性がある。

 

/**
 * jquery.myplug マルチメソッド(別案1)
 *
 * SYNOPSIS
 *
 * $('#target').myplug.method1();
 * $('#target').myplug.method2();
 *
 */
;(function($) {
    $.fn.myplug = {};
    $.fn.myplug.method1 = function(params) {}
    $.fn.myplug.method2 = function(params) {}
})(jQuery);

上記の例では、複数のメソッドを含む構造体を$.fn.myplugという名前で定義している。プラグイン間の衝突は回避できたけど、メソッド内でどうjQueryオブジェクトを取得するのか分からない。

 

/**
 * jquery.myplug マルチメソッド(別案2)
 *
 * SYNOPSIS
 *
 * $('#target').myplug().method1();
 * $('#target').myplug().method2();
 * $("#target").myplug().method1().myplug().method2();
 */
;(function($) {

    $.fn.myplug = function () {
        var jq = this;
        return {
            method1 : function(){return jq;},
            method2 : function(){return jq;}
       }
    };
})(jQuery);

thisの代わりにjqにjQueryオブジェクトを格納。インターフェースがイマイチだけど、まあまあ有り得る形。

 

/**
 * jquery.myplug 公式ドキュメント方式
 *
 * SYNOPSIS
 *
 * $('#target').myplug('method1', params);
 * $('#target').myplug('method2', params);
 */
;(function($) {

    var plugname = 'myplug';

    // 全メソッドをここに定義
    var methods = {
        init    : function(params){return this;},
        method1 : function(params){return this;},
        method2 : function(params) {return this;}
    };

    // 以下は共通
    $.fn[plugname] = function(method) {
        if ( methods[method] ) {
            return methods[ method ]
                .apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.' + plugname );
            return this;
        }
    };
})(jQuery);

jQuery公式ドキュメントでも推奨しているらしい方式。メソッド内ではthisがjQueryオブジェクトだし、開発は今までどおりで分かりやすい。ただ、メソッド名が文字列っていうのは何となく抵抗がある。

 

/**
 * jquery.myplug インナークラス方式
 *
 * SYNOPSIS
 *
 * var instance = $('#target').myplug(params);
 * instance.method1();
 * instance.method2();
 * instance.method1().method2();
 *
 * var instance2 = $('#target').data('myplug'); // can get the instance later
 */
;(function($) {

    /**
     * プラグインの名称
     */
    var plugname = 'myplug';

    /**
     * 全プラグイン共通のインナークラスコンストラクタ
     */
    var Class = function(elem, params){
        this.elem = elem;
        this.elem.data(plugname, this);
        this.params = params;
    }

    /**
     * メソッドを定義
     */
    Class.prototype.method1 = function(params){
        return this;
    };
    Class.prototype.method2 = function(params) {
        return this;
    };

    /**
     * デフォルトパラメータ
     */
    var default_params = {

    };

    /**
     * 全プラグイン共通
     */
    $.fn[plugname] = function(params){
        return new Class(this, $.extend(default_params, params, {}));
    }
})(jQuery);

プラグインひとつにつき、インナークラスを一つ定義する。元ネタ。インターフェースは、DOMに紐づいたmyplugインスタンスを引き回すのが特徴で、メソッドチェーンもできる。myplugインスタンスは$('#target').data('myplug')などとすれば後から取得できる。広く普及している、DOMを引き回すような方式とは異なるので混乱の元かも知れないけど、今のところ一番無難な方式の気がする。

ここまで来ると、プラグイン間で継承できるようなフレームワーク作ってみようとか、夢が広がる。

 

あと、同じ名前空間つながりで、イベント処理も必ず$('#target').bind('click.'+plugname, func)ってやっておいたほうが身のためです。すでに痛い目みた。

jQueryプラグインをうまいことカプセル化する設計方法」への5件のフィードバック

  1. cyokodog

     
    自分も昔プラグインの定義方法についてはいろいろ模索してました。

    http://d.hatena.ne.jp/cyokodog/20091126/define_plugin01
    http://d.hatena.ne.jp/cyokodog/20091209/define_plugin02

    公式推奨パターンはjQuery UIもこれ系ですが、メソッド名が文字列っていうのが抵抗ありますよね・・・そんな訳で、自分も一番下の方法をも少し拡張した感じで使ってます。ただメソッドの返却値がjQueryオブジェクトでないっていうは抵抗があるので、jQuery TOOLSにならいパラメータで api:true とした時のみインナークラスのインスタンスを返すようにしてます。

     http://d.hatena.ne.jp/cyokodog/20091225/define_plugin04

    
    
    
    
    
    
     
    
    

    返信
  2. jamadam 投稿作成者

    ブログ拝見しました。すごい情報量ですね。先に読んでいれば色々苦労しなかったかもしれない・・。後で詳しく読ませて頂きます。

    返信
  3. ピンバック: Topics 20101107 | Real Topics

  4. ピンバック: coffeescriptで複数メソッドあるjQuery pluginのひな形 | こぎつねぶろぐ

  5. ピンバック: [jQueryプラグイン] inputタグの数値(金額)をカンマ区切り | RedStub

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です