プラグイン API の定義パターンについて調べてみた

jQuery の標準 API が DOM 操作等のコア機能に特化しているのに対し、プラグインとして提供される機能には、ウィジェット生成処理をはじめとした拡張機能的なものが多々あります。

また、このようなプラグインの中には内部的に生成した要素やメソッドに対し、外部から参照、実行が可能な API を用意し、プラグインに柔軟性や拡張性を持たせてるものもあります。

前回のエントリ(jQuery プラグインの定義パターンについて調べてみた)同様、公開されてるプラグインのソースを参照し、どのようなプラグイン API の定義パターンがあるか調べてみました。

ColorBox のプラグイン API を実際に使ってみる

定義パターンを見る前に、画像ギャラリー系プラグイン ColorBox の提供するプラグイン API を使用し、どんなことができるのか試してみます。

ColorBox は、画像ファイルにリンクする a 要素に対し colorbox() メソッドを実行することで、画像ビューワー機能を持たせることができます。

Demo

colorbox() メソッドの対象要素が複数あると prev / next のリンクのクリックで表示画像を切り替えることができます。サンプルのデザインの場合、画像切替リンクが小さく目立たないので、プラグイン API を使用し自前で大きめの画像切替リンクを設けてみることにします。

Demo

HTML

<div class="container">
	<ul>
		<!-- 画像ファイルのリンクリストの定義 -->
		<li><a href="..." title="..." rel="ex01">image1</a></li>
		<li><a href="..." title="..." rel="ex01">image2</a></li>
		<li><a href="..." title="..." rel="ex01">image3</a></li>
		<li><a href="..." title="..." rel="ex01">image4</a></li>
	</ul>
</div>

CSS

/* コンテナ */
div.container{
	margin : 0 auto;
	width : 800px;
	/* 画像切替リンクがコンテナ内に収まるようにします */
	position : relative;
}
/* 画像切替リンク */
a.prevImg,a.nextImg{
	/* オーバーレイに隠れないように z-index の値を大きく設定 */
	z-index:99999;
	position:absolute;
	display:block;
}

JavaScript

jQuery(function($){
	//colorbox メソッドの実行
	$("a[rel='ex01']").colorbox();

	//画像切替リンクの作成	
	var prev = $('<a class="prevImg">PREV</a>').css('left',0);
	var next = $('<a class="nextImg">NEXT</a>').css('right',0);
	var navi = $([prev[0],next[0]]).hide().appendTo(
		$.browser.msie ? $('body') : $('div.container')	// for IE z-index bug
	);

	//画像切替イベントの割当て
	prev.click(function(){ $.fn.colorbox.prev()}); // API
	next.click(function(){ $.fn.colorbox.next()}); // API

	//画像切替リンクの表示・非表示処理
	$(document)
		.bind('cbox_open',function(){ // API
			//表示
			navi.css({
				'top' : ($('html').attr('clientHeight')) / 2 + $(window).scrollTop() - 50
			}).show('slow');
		})
		.bind('cbox_closed',function(){ // API
			//非表示
			navi.hide('slow');
		});
});

生成した画像切替リンクの click ハンドラにて、プラグイン API である画像切替メソッド $.fn.colorbox.prev() / $.fn.colorbox.next() を実行するようにします。

また、画像が表示状態になった時に発生するカスタムイベント cbox_open では、画像切替リンクを表示するようにし、画像を閉じた時に発生するイベント cbox_closed では、画像切替リンクを非表示状態にしています。

では、実際にプラグイン API の定義パターンを見てみます。

1. イベント割り当て API

プラグインにより生成された要素等に、イベント処理を割り当てたい場合に使用する API です。

1.1 プラグイン初期化処理におけるイベントの割り当て
$(element).myPlugin({ onHogeClick : function(){ ... }})

実装例

$.fn.myPlugin = function( opt ){
	//...
	$(hoge).clcik(function(){
		opt.onHogeClick();
		// あるいは opt.onHogeClick.apply(this,[ ... ] ) など
	});
}

プラグインの初期化パラメータとして、コールバック関数を指定することでイベントを割り当てる方法です。もっともポピュラーな方法かと思われます。
当然ですが、割り当てるイベントの内容は初期化処理の時点で書けることしか書けません。Ajax 等で動的に取得した値や要素などをイベント内で参照するにはロジックに工夫が必要になります。

1.2 bind メソッドによるイベントの割り当て
$(element).myPlugin();
//...
$(element).bind('myPlugin_onHogeEvent' , function(){ ... });

実装例

$.fn.myPlugin = function( opt ){
	//...
	$(hoge).clcik(function(){
		$(this).trigger('myPlugin_onHogeEvent');
	});
}

jQuery 標準メソッドである bind / unbind を使用することにより、プラグインの独自仕様(例えばイベント関数内における this は何を指すかなど)を覚えるコストが軽減されます。
また、プラグインの実行対象となった要素が分かっていれば、任意のタイミングによるイベントの割り当てと解除が可能です。
イベント名は、他のプラグインと競合しないようなネーミングの工夫が必要になります。

1.3 jQuery.fn を汚染した API によるイベントの割り当て
$(element).myPlugin();
//...
$(element).onHogeClick(function(){ ... });
$(element).execHoge();

実装例

$.fn.extend({
	myPlugin = function(){ ... },
	onHogeClick = function( f ){
		f();
		// あるいは f.apply(this,[ ... ] ) など
	},
	execHoge : function(){ ... }
});

プラグインの独自 API 自体も jQuery プラグインとして定義してしまう方法です。他のプラグイン名(あるいは将来 jQuery のコアAPI として定義されそうな名前)と競合しないようなメソッド名で定義する必要があります。
イベント割り当て用途以外のメソッドも定義されたります。

2. API オブジェクト経由によるメソッドの実行

プラグインが提供する各種機能を実行するためのメソッド郡を、独自の API オブジェクトを経由して利用者に提供する実装です。

2.1 API オブジェクト直接参照によるメソッドの実行
$(element).myPlugin();
var API = $.fn.myPlugin // あるいは $.myPlugin など
API.execHoge();

実装例

$.fn.myPlugin = function(){ ... };
$.extend($.fn.myPlugin , {
	execHoge : function(){ ... },
	//...
});

プラグインメソッドの直下に定義された API オブジェクトを直接参照し、そこに定義されたメソッドを実行します。この場合 API オブジェクトは1つしか存在せず、プラグイン実行対象となった要素とは 1 : N の関係になります。そのため API メソッドの実装においては、その時点において処理対象となる要素の管理ロジックが必要になります。
ColorBox では、要素毎の個別パラメータを data() メソッドで保持し、表示中の画像に対応したパラメータに基づきメソッドが処理されるように実装されてます。

2.2 パラメータ指定による API オブジェクトの取得
var API = $(element).myPlugin({ api : true }); // あるいは .myPlugin('api');
API.execHoge();

実装例

//簡略化して記述してます
var myPlugin = function(){ ... }
$.extend(myPlugin.prototype,{
	execHoge = function(){ ... },
	//...
}
$.fn.myPlugin = function( opt ){
	var api = new myPlugin();
	//...
	return opt.api ? api : this;
}

プラグインメソッドに対し、API オブジェクトの要求をパラメータ指定により通知することで、API オブジェクトを取得します。
プラグインメソッドの返却値は、jQuery オブジェクトであることがプラグイン実装上の暗黙のルールですが、パラメータの記述が、jQuery オブジェクト以外の返却値になることを明示することになります。
ちなみ qTip では、date() メソッドにより、プラグイン実行対象となった要素毎に個別パラメータを保持することで、任意のタイミングによる API オブジェクトの取得を可能にしてます。

2.3 コールバック関数経由の API オブジェクトの取得

プラグイン実行例

$(element).myPlugin({ onHogeClick : function( api ){
	api.execHoge();
}});

イベント割り当て処理等のコールバック関数の起動で、API オブジェクトを引数として受け取ることで API オブジェクトを取得します。

2.4 API オブジェクトのメソッドをラッパーメソッド経由で実行

プラグイン実行例

$(element).myPlugin({});
//...
$(element).myPlugin('execHoge', //... );
//簡略化して記述してます
$.myPlugin = function(){ ... }
$.extend($.myPlugin.prototype,{
	execHoge = function(){ ... },
	//...
}
$.fn.myPlugin = function( opt ){
	this.each(function(){
		if( opt == メソッド名 ) {
			$(this).data('myPlugin')[opt]();
		}
		else{
			$(this).data('myPlugin' , new $.myPlugin() )
		}
	});
}

プラグインメソッドの第一引数が、API オブジェクトで管理するメソッド名だった場合は当該メソッドを実行し、それ以外の場合はプラグインの初期化処理を行います。
サンプルの実装の場合、要素個別の API オブジェクトを data() メソッドで保持することになるので、任意のタイミングによるプラグイン API の実行が可能です。
APIプラグインメソッドにラップされているため、プラグイン利用者は API オブジェクトというものを意識せずに各種 API を利用することができます。

どの定義パターンがいいか?

やはり個人的な好みになりますが...イベント API については、イベント割り当て処理に重きを置かないケースは「1.1 プラグイン初期化処理におけるイベントの割り当て」で済まして

$.fn.myPlugin = function( opt ){
	//...
	$(hoge).clcik(function(){
		opt.onHogeClick();
		// あるいは opt.onHogeClick.apply(this,[ ... ] ) など
	});
}

必要に応じて「1.2 bind メソッドによるイベントの割り当て」というとこでしょうか。

$(element).myPlugin();
$(element).bind('myPlugin_onHogeEvent' , function(){ ... });

API オブジェクトの方は...う〜ん、迷いますが実装を手軽に済ますなら「2.3 コールバック関数経由の API オブジェクトの取得」で

$(element).myPlugin({ onHogeClick : function( api ){
	api.execHoge();
}});

欲をかけば jQuery TOOLS の「2.2 パラメータ指定による API オブジェクトの要求」や

var API = $(element).myPlugin({ api : true }); // あるいは .myPlugin('api');
API.execHoge();

jQuery UI の「2.4 API オブジェクトのラッパーメソッドによる実行」と言いたいところですが

$(element).myPlugin({});
$(element).myPlugin('execHoge', //... );

実際の実装を見ると jQuery UI の方は jQuery コアの API に近い良く考えられたつくり(対象要素が複数件あった場合や、ゲッターメソッド実行時の処理など)で理想的ではあるけど、その分実装するとなると骨も折れるしソースが膨らむような気がします。
jQuery TOOLS の実装は、API オブジェクトの返却処理に手抜き感はありますが、ラッパーも必要としない素直なつくりなので、プラグインという位置付けや諸々のバランスを考えると、jQuery TOOLS をベースに jQuey UI 的な配慮を加味した実装...ていうのができればいいのかな?...とも思います。その辺は今後の課題としたい思います。