“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)ってやっておいたほうが身のためです。すでに痛い目みた。