トップ «前の日記(2006-03-09) 最新 次の日記(2006-03-11)» 編集

2002|01|02|03|04|05|06|07|08|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|02|03|04|07|

2006-03-10 [長年日記]

_ HTML_AjaxHandlerのプロトタイプ

昨日言っていたHTML_AjaxHandlerのプロトタイプ版を作ってみた。

デモのソースは複数のデモをまとめて書いてあって読みにくいんで、2行目に表示されている平方根の計算に関連する部分だけ抜き出して、解説してみる。

デモの内容としては、テキストボックスに数字を入れると、その右側にリアルタイムで平方根の計算結果が表示される、というもの。平方根の計算はサーバーサイドで行い、XmlHttpRequestでデータをやりとりしている。Ajaxのデモなんで、平方根なんてJavaScriptで計算すればいいじゃん、ってツッコミはなしね。

HTMLとして必要な要素は、以下。

<script src="prototype.js" type="text/javascript"></script>
平方根: <input type="text" name="rootnum" id="rootnum" value="" /> = <span id="rootnum_result"></span>

ここでは特にイベントハンドラー等は定義していないし、prototype.jsを読み込んでいる以外は、JavaScriptコードは書いていない。

続いて、PHPコード部分は、以下。

require_once 'HTML/AjaxHandler.php';
$ajax =& new HTML_AjaxHandler();
$ajax->addHandler(
	'rootnum',
	'keyup',
	array(
		'url' => 'api.php',
		'method' => 'get',
		'parameters' => 'command=root',
		'container' => 'rootnum_result',
	)
);
echo $ajax->toJavaScript();

これでイベントハンドラーの定義を行っている。

HTML_AjaxHandler::addHandlerの引数は、

  • $elementId - 対象となる要素のID
  • $eventName - ハンドリングするイベント(prototype.jsの仕様に合わせる)
  • $ajaxOptions - その他オプションいろいろ

となっている。$ajaxOptionsに関しては、基本的にprototype.jsのAjax.Request、Ajax.Updater、Ajax.PeriodicalUpdaterのオプション(およびパラメータ)を受け付ける。それ以外にも、HTML_AjaxHandler独自のオプションをいくつか受け付ける。

上記の例だと、以下のような感じ。

  • url - XmlHttpRequestするURL。
  • method - リクエストメソッド。
  • parameters - 初期パラメータ。対象の要素がフォームやフォーム要素の場合は、その内容も自動的に追加される。
  • container - Ajax.Updaterで更新される要素のID。containerが指定されている場合、Ajax.Requestではなく、Ajax.Updaterが呼ばれることになる。containerに加えてfrequencyが指定されている場合は、Ajax.PeriodicalUpdaterが呼ばれることになる。

で、最後のecho $ajax->toJavaScript()で、実際のイベントハンドラー等のJavaScriptコードを自動生成して出力している。どういうJavaScriptコードかというと、以下のような感じ。

<script type="text/javascript">
<!--
function AjaxHandler_autoParameters(e, params)
{
	if (params == undefined) {params = '';}
	if ($(e).value != undefined) {
		params += (params != '' ? '&' : '') + Form.Element.serialize($(e));
	} else if ($(e).tagName.toLowerCase() == 'form') {
		params += (params != '' ? '&' : '') + Form.serialize($(e));
	}
	return params;
}

function AjaxHandler_autocreated_rootnum_keyup(event)
{
  var url = 'api.php';
  var options = {
    method: 'get',
    parameters: 'command=root',
    asynchronous: true,
    container: 'rootnum_result',
  };

  options['parameters'] = AjaxHandler_autoParameters('rootnum', options['parameters']);
  var ajaxRequest = new Ajax.Updater(
    'rootnum_result',
    url,
    options
  );
  Event.stop(event);
}

function AjaxHandler_autocreated_441130d9062b1_onInit()
{
  Event.observe('rootnum', 'keyup', AjaxHandler_autocreated_rootnum_keyup, false);
}

Event.observe(window, 'load', AjaxHandler_autocreated_441130d9062b1_onInit, false);

-->
</script>

その他のデモは、

  • load時にAjax.PeriodicalUpdaterに登録して、現在時刻をAjaxで取得&表示する。loadの処理が微妙(DOM要素単位でもloadイベントがあると勘違いして作っていて、あとでそんなイベントはないことに気づいて、あわててごまかした産物)。
  • 二つのテキストボックスに入力された数値を、フォームのsubmitをフックして、足し算した結果を表示する(フォーム投稿処理をAjaxで代替するイメージ)
  • 二つのテキストボックスに入力された数値を、入力欄の更新をフックして、足し算した結果を表示する(パラメータの付け替えとか、複数のイベントハンドラで同じコールバックを使ってみたりとか)

なんて感じ。

まだ全然細かいところまで練れていないけれども、ひとまずこんな感じのアプローチで、Ajax対応が楽になるかどうかいろいろ使って試してみよう。一応HTML_AjaxHandlerのライセンスはLGPLにしてあるけど、まだプロトタイプレベルなんで、コードは全面的に変更される可能性がある。

ちなみに

なんで初期化(イベントハンドラの登録)をwindow.onload経由でやっているかというと、JavaScriptコードをHTMLヘッダ部に出力した場合、イベントハンドリングする対象のDOM要素がまだ生成(HTMLとして出力)されていない可能性があるから。

あと、XmlHttpRequestリクエストを実行する前に呼ばれる独自のコールバックオプションとして、parametersCallback、urlCallback、optionsCallbackが使える。それぞれ、Ajax.*を呼び出す直前に、JavaScriptコードでparameters、url、optionsを書き換えたいときに使う。

本当はもう一個、XmlHttpRequestを行うかどうかを選択するためのコールバックも必要かと思っていたんだけど、基本的な処理の流れ自体を分岐するオプションに関しては、それ以外にもいろいろなパターンがありそうだから、もっといろいろ具体的なパターンを考えてから実装した方が良さそうに思えたんで、今のところつけていない。