jQuery オブジェクトを汚染せずにプラグインを追加する方法を考えてみた
気軽にプラグインを追加したい
jQuery のプラグインっていうと、モーダルウィンドウやツールチップを表示したり、データをグラフ化したりとそれだけで大きな恩恵を受けられるものが多々ありますが、自分なりのプラグインの定義方法などの汎用的な処理を考えていると、jQuery のメソッドをちょっと拡張したような自分用メソッド郡みたいなものを定義したくなることがあります。
また、複数のパーツで構成される UI 系のプラグインの場合だと各パーツ毎に専用メソッドを持たせたくなったりします。例えばモーダルウィンドウの場合なら、閉じるボタンには winClose メソッド、ウィンドウフレーム部には、winResize メソッドみたいなものです。ところが、
プラグインの追加 = jQuery の prototype オブジェクトへの関数の追加 = jQuery オブジェクトの汚染
と考えると上記のようなケースでは気軽にプラグイン(メソッド)を追加する気になれません。いろんなプラグインなどを入れてる場合だと名前競合なども気になります。だったら prototype に追加せずインスタンス化された jQuery の DOM ノードオブジェクト(って言うんですかね?)そのものにメソッドを追加するという方法もありますが、この場合だとイベントの起きたパーツに紐付く他のパーツを処理しようとした時など eq() メソッド経由で参照することになり結局メソッドを実行するには一工夫必要になったりします。
jQuery オブジェクトの複製
では、jQuery オブジェクトを必要に応じ複製(継承)し、その複製した jQuery オブジェクトに必要なメソッド(プラグイン)を追加してはどうか?
で、その中で上記の UI 紐付き問題等を容易に解決するメソッドを作ってはどうか?
という事でいろいろと頭をひねったのですが、jQuery の複製(というか継承)が、なかなかうまくいかず悩んでたところ、文殊堂さんのところにこんな記事が・・
testという名前のメソッドを呼ぶとそれ以降、jQueryオブジェクトをラップしたオブジェクトによってメソッドチェーンが繋がっていく。
条件付メソッドチェーン実現のためjQueryのラッパーを作ってみた - 文殊堂
jQueryオブジェクトを返さないメソッドを実行した場合には、ラップしないで元のメソッドの戻り値を返す。
apply でjQueryメソッドを実行した結果が、jQuery インスタンスのものだったらラッパーオブジェクトで再取得するというシンプルなものでしたが、自分にとっては目からうろこでした。
9 割方解答そのものな記事でしたが、jQuery の複製として使うにはこのままだと若干問題があったので少々手を入れさせていただき、セレクタ部の処理を追加し、プラグイン化してみました。
使用例
こんな感じで生成します。
var $j1 = $.MyJQ()
後は、$j1 が jQuery オブジェクトと同じように使えます。
必要なメソッドを追加します。
$j1.fn.pink=function(){ this.css('background','pink') alert('pink') return this; } $j1('body').pink()
継承もできます。
$j2=$.MyJQ() $j2.fn.extend($j1.fn) $j2.fn.silver=function(){ this.css('background','silver') alert('silver') return this; } $j2('body').pink().silver().pink()
jQuery オブジェクトは汚染されません。
alert($.fn.pink) //undefined alert($.fn.silver) //undefined
ソース
セレクタ部は、apply とかで jQuery の処理を間借りしたかったのですが、良い方法が思い浮かばず自前で書いてしまっています。
(function($j){ $j.MyJQ = function(){ return $j.MyJQ.impl.selector() } $j.MyJQ.impl = { selector : function(){ var s = function(expr){ var o=this,arr=[]; if(this instanceof arguments.callee){ o._$j=expr.each(function(idx){ arr[idx]=this; }) return $j.extend(arr,this) } else return new arguments.callee($j(expr)) } for(var i in $j)if($j.isFunction($j[i]))s[i]=$j[i]; return this.jqWrap(s); }, jqWrap : function(s){ s.fn=s.prototype; for(var i in $j.fn) if($j.isFunction($j.fn[i])) (function(i){ s.fn[i] = function(){ var target = this.constructor == s ? this : this._$j; var ins = $j.fn[i].apply(target,arguments) if(!(ins instanceof $j))return ins; if(ins==target){ var _ins = new s(ins); for(var j in this) if(typeof _ins[j]=='undefined' && this.hasOwnProperty(j))_ins[j]=this[j] return _ins; } return new s(ins) } })(i) return s; } } })(jQuery)