jQueryプラグインの書き方を考えてみる(2)
更新履歴
- 2010-01-21
- プラグイン定義方法については、約1年後に再考した下記エントリの方をお勧めします。
jQuery 用にカスタマイズした myclass.js を使用して、プラグインの定義方法を考えてみます。
myclass.js による クラス定義
myclass.js を使用するとクラスベースな OOP な書き方ができます。
(function(target){ target.Sample = MyClass.create({ init: function(msg){ this.msg = msg; return this; }, method: function(msg){ alert(msg||this.msg) return this; } }) })(window); (function(){ Sample('default').method().method('parameter'); })();
継承もできます。
(function(target){ target.Sample2 = MyClass.create(target.Sample,{ //第一引数に親クラスを指定 init: function(node,msg){ this.node=node; this.$super.init(msg); //親クラスのコンストラクタの実行 return this; }, setMsg: function(msg){ this.node.innerHTML=msg||this.msg; return this; } }) })(window); (function(){ window.onload=function(){ Sample2(document.getElementById('sample1'),'default').setMsg(); } })();
myclass.js の詳細はこちらをどうぞ。
自分用class定義ライブラリmyclass.jsを作ってみる(1) (2) (3) (4)
myclass4jquery.js によるプラグイン定義
myclass.js を jQuery 用にカスタマイズした myclass4jquery.js でプラグイン定義してみます。jQuery.MyClass.create メソッドを使用し以下のよう定義します。
(function($j){ $j.Sample = $j.fn.Sample = $j.MyClass.create({ init: function(msg){ this.msg = msg; return this; }, method: function(msg){ alert(msg||this.msg) return this; } }) })(jQuery) jQuery(function($j){ $j.Sample('none node').method(); $j('#sample1,#sample2').Sample('has node').method(); })
継承
myclass.js と同様、第一引数に親クラスを指定する事で継承も可能です。
(function($j){ $j.Sample2 = $j.fn.Sample2 = $j.MyClass.create($j.Sample,{ init: function(msg){ this.$super.init(msg); return this; }, setMsg: function(){ this.$j().html(this.msg); return this; } }) })(jQuery) jQuery(function($j){ $j.Sample2('none node').method(); $j('#sample1,#sample2').Sample2('has node').setMsg(); })
jQueryDOMノードとプラグインオブジェクトの参照
myclass4jquery.js でプラグインオブジェクトを定義した場合、jQueryで取得したjQueryDOMオブジェクトと、プラグインオブジェクトが共存する事になります。これらのオブジェクトを参照する場合は自動生成される getter メソッドを使用します。
jQueryDOMオブジェクトを取得する場合は $j メソッド、プラグインオブジェクトを取得する場合は my メソッドを使用します。
jQuery(function($j){ $j('#sample1,#sample2').Sample() .my().method('set blue') .$j().css('background','blue') .my().method('set red') .$j().css('background','red') ; })
前回 (jQueryプラグインの書き方を考えてみる(1)) のサンプルプログラムを myclass4jquery で書き換えてみます。
$j.Sample = $j.fn.Sample = $j.MyClass.create({ init: function(opt){ this.opt = $j.extend({ max: 200, min: 100 }, opt); return this.build(); }, build: function(){ var my = this; my.$j() .wrap('<div style="border:solid 10px #fafafa;margin:10px;"></div>') .css('border', 'solid 10px #eee').html('click!') .wrapInner('<div style="border:solid 10px #ddd;background:#ccc"></div>') ; var out$j = my.$j().parent(), in$j = my.$j().find('> div'); $j('<button>click!</button>').prependTo('body').click(function(){ out$j.width() == my.opt.min ? my.max() : my.min(); }); in$j.click(function(){ var idx = my.inFrame().index(this); return my.outFrame(idx).width() == my.opt.min ? my.max(idx) : my.min(idx); }) out$j.each(function(idx){ $j(this).css('margin-left', idx * my.opt.min); }) my.crossBind({ getter: { outFrame: out$j, inFrame: in$j }, method: ['min', 'max'] }); return this.$j(); }, resize: function(size, idx){ this.outFrame(idx).animate({ width: size }) this.inFrame(idx).animate({ height: size - 40 }) return this; }, min: function(idx){ this.resize(this.opt.min, idx).resize(this.opt.max, idx).resize(this.opt.min, idx); return this; }, max: function(idx){ this.resize(this.opt.max, idx).resize(this.opt.min, idx).resize(this.opt.max, idx); return this; } }) })(jQuery) jQuery(function($j){ $j('#sample1,#sample2').Sample() .inFrame().css('background', 'red') .outFrame().css({ 'background': 'blue', 'padding': '10px' }) .min() ; })
前回解決できなかった問題
これらが解決されてます。
これは内部的に生成される crossBind メソッドで解決してます。
my.crossBind({ getter: { outFrame: out$j, inFrame: in$j }, method: ['min', 'max'] });
getter には、ハッシュ形式で getter メソッド名と 参照したいオブジェクトを指定し、method には、メソッドチェーンの対象としたいメソッド名を 配列で指定します。以下のようなメソッドチェーンな記述が可能になります。
$j('#sample1,#sample2').Sample().inFrame().$j().outFrame().min().inFrame().my();
インデックス指定による jQueryDOMノードの再取得
jQueryを使用するとサンプルのように複数のDOMノードを容易に取得する事ができますが、取得後、n 番目のノードのみを再取得したくなる場合があります。そのような場合、
$j('div').filter(':eq(1)')
のように記述すれば取得可能ですが、myclass4jquery.js でプラグイン定義した場合に同様の記述を行うと、前述の crossBind メソッドによって関連付けられたメソッドチェーンが使用できなくなってしまいます。この対策として、getter メソッドの引数に取得したいノードの番号( n )を指定する事で対象ノードが jQueryDOMノードとして再取得され、且つ、メソッドチェーンも継続して使用できるようになります。
jQuery(function($j){ $j('#sample1,#sample2').Sample() .inFrame().css('background', 'red') .outFrame(0).css({ 'background': 'blue', 'padding': '10px' }).min().max(1) ; })
myclass.js、myclass4jquery.js のソース
以下のようなつくりになっています。
修正履歴
- 2008.9.9
- :三世代以上の継承時、親オブジェクト内($super)で、先祖オブジェクト($super.$super)の参照ができなかったので修正。継承時、親オブジェクト内($super)で $j , my メソッドの参照ができなかったので修正。
- 2008.9.20
- 継承によるクラス定義の場合、動的に生成、割り当てされた getter や method の 子クラスへの伝播等に問題があったので修正。詳細は次のエントリにて。
myclass.js
(function(){ window.MyClass = { create : function(c,e){ var f = function(){ var ins=MyClass.getMyInstance(f); if(ins.init)return ins.init.apply(ins,arguments); return ins; } f.__member__ = e||c; if(e)e.__c__=c; return f; }, getMyInstance : function(f){ var util = MyClass.MyUtil; if (f.__member__.__c__) { var ins = MyClass.getMyInstance(f.__member__.__c__) var $super = ins; ins = util.extend( util.getInstance($super),util.getInstance(f.__member__,true) ); ins.$super = $super; return ins; } return util.getInstance(f.__member__,true); } } window.MyClass.MyUtil = { extend : function(obj,ext){ for(var i in ext)obj[i]=ext[i]; return obj; }, getDeepInstance : function(obj){ if (obj.constructor === Object) { var f = function(){}; for(var i in obj)f.prototype[i]=MyClass.MyUtil.getDeepInstance(obj[i]); return new f(); } else return obj; }, getInstance : function(obj,deep){ var f; (f = function(){}).prototype = obj; obj = new f; if(deep)for(var i in obj)obj[i]=MyClass.MyUtil.getDeepInstance(obj[i]); return obj; } } })();
myclass4jquery.js
(function($j){ $j.MyClass = { create : MyClass.create({ init : function(c,e){ var o=this; var f = MyClass.create(c,e); var r = function(){ var n$j=this; var ins=$j.MyClass.getMyInstance(f,n$j); return ins.init.apply(ins,arguments); }; r.__member__ = f.__member__; return r; } }), getMyInstance : function(f,n$j){ var o=this; var _init = f.__member__.init; f.__member__.init=function(){return this}; var ins=f.apply(n$j,arguments); if(_init)ins.init = f.__member__.init=_init; o.nestedBind(ins, o.crossBind(ins,{ getter : { $j : n$j } }) ); return ins; }, nestedBind : function(ins,getter){ var o=this; $j.extend(ins,getter); ins.crossBind = function(opt){ return o.crossBind(ins,opt); } if(ins.$super)o.nestedBind(ins.$super,getter); }, crossBind : function(ins,opt){ var o=this; if(!ins.__getter__)ins.__getter__={}; if (opt.getter) { for(var n in opt.getter)ins.__getter__[n]=o.getReGetter(n,opt.getter[n],ins); ins.__getter__.my=function(){return ins;} } if(opt.method){ ins.__method__=ins.__method__||{}; for (var i = 0; i < opt.method.length; i++) { var name=opt.method[i]; ins.__method__[name] = o.getMethod(ins, ins[name]) } } for (var n in ins.__getter__) { var obj=ins.__getter__[n](); $j.extend(obj, ins.__getter__); if (ins.__method__) { $j.extend(obj, ins.__method__); } } return ins.__getter__; }, getMethod : function(ins,f){ return function(){ return f.apply(ins,arguments); } }, getReGetter : function(name,obj,ins){ var o=this; return function(idx){ var newObj = o.reget(obj,idx); $j.extend(newObj,ins.__getter__); if(ins.__method__)$j.extend(newObj,ins.__method__); return newObj; } }, reget : function(obj,idx){ if(!(obj instanceof $j))return obj; if(!obj.__allObj__)obj.__allObj__=obj; if(idx==undefined)return obj.__allObj__; var newObj=obj.__allObj__.filter(':eq('+idx+')') newObj.__allObj__=obj.__allObj__; return newObj; } } })(jQuery);