PerlでAjax(CGI-Ajax編) http://www.ark-web.jp/sandbox/wiki/178.html
目次 †
prototype.jsの利用例:マウスオーバー時にテキストを編集できるようにするでは、お問い合わせフォームの確認画面上でテキストを編集可能にするためにprototype.jsと利用しました(prototype.jsの利用例なので当然ですが^^;)
prototype.jsはRailsと親和性の高い有用なライブラリですが、裏を返すとPerl等で作られた古いアプリケーションのAjax化にはそれほど親和性は高くないということで・・・。レガシーなアプリのAjax化にはprototype.js(の直接利用)は必ずしも適さないかもと感じました。そこで、レガシーなPerlアプリをAjax化する際に使えそうなライブラリを探して触ってみましたのでお伝えします。CGI::AjaxとHTML::Prototypeという二つのライブラリをとりあげる予定です。今回は、まずCGI::Ajaxをとりあげます。
なお、先日実際にPerlで書かれたお問い合わせフォームプログラムのAjax化をprototype.jsで試みてみたのですが、クライアント側のprototype.js(およびJavaScript)、HTMLソースの修正とサーバ側のPerlスクリプト修正を行ったりきたりしながら、Ajaxを組み込むHTMLタグのidとprototype.js、JavaScriptとの同期をとり、さらに同期をとったidをPerlスクリプトが吐くようにする、prototype.jsを使ったコードをPerlスクリプトから吐くようにする、etc・・・を行わなければならず非常にメンドイです。
CGI-Ajax †
CGI::AjaxはHTMLページ上に発生したJavaScriptイベントをトリガーに非同期にperlのサブルーチンをコールすることを可能にします。このために、まず、下記のようにJavaScriptのファンクション名とPerlサブルーチンのリファレンスとをマッピングさせます。
my $cjx = new CGI::Ajax( 'JSFUNC' => \&PERLFUNC );
JSFUNCはJavaScriptのファンクション名、PERLFUNCはJSFUNCにマッピングさせるPerlサブルーチンです。
次に、JSFUNCを起動するトリガー(JavaScriptイベント)をセットします。ここでは、onClickを例に示してみます。
onClick="JSFUNC(['source1','source2'], ['dest1','dest2']);"
ここまでの記述によって、onClickイベント発生時にJSFUNCが起動され、JSFUNCにマッピングされたPerlのサブルーチン(PERLFUNC)がコールされます。
JSFUNCの引数
['source1','source2'], ['dest1','dest2']
のsource1、source2、dest1、dest2はHTMLページ内のinput、およびdivのidです。
<input type=text id=source1 /> <input type=text id=source2 /> <div id=dest1></div> <div id=dest2></div>
引数は[]でかこまれたものが二つありますが、最初の[]は入力に相当し、二つ目の[]は出力に相当します。
つまり、
onClick="JSFUNC(['source1','source2'], ['dest1','dest2']);"
と表記すると、JSFUNCへの入力としてsource1、source2が指し示すinputへの入力データを渡し、結果をdest1、dest2が指し示すdivに出力することになります。JSFUNCはPERLFUNCとマッピングされているため、source1、source2がPerlサブルーチン(PERLFUNC)の引数になり、Perlサブルーチン(PERLFUNC)の結果がdest1、dest2に返されることになります。
もっともシンプルな例(一つの入力に一つの出力) †
シンプルな例を示します。下のプログラムを適当な名前で保存し、ブラウザからCGIとして実行するとテキストボックスが表示されます。表示されるテキストボックスに文字列を入力すると(=JavaScriptのkeyupイベントが発生すると)、「[入力した文字列]が入力されました」と画面に非同期で表示します。
#!/usr/bin/perl use strict; use CGI; use CGI::Ajax; use Jcode; my $cgi = new CGI; my $cjx = new CGI::Ajax( 'exported_func' => \&perl_func ); print $cjx->build_html( $cgi, \&show_HTML ); sub perl_func { my $input = shift; my $output = $input . "が入力されました"; Jcode::convert(\$output, 'utf8', 'euc'); return $output; } sub show_HTML { my $html = <<EOHTML; <html> <body> 何か入力してください。 <input type="text" name="val1" id="val1" onkeyup="exported_func( ['val1'], ['resultdiv'] );"/> <br/> <div id="resultdiv"></div> </body> </html> EOHTML Jcode::convert(\$html, 'utf8', 'euc'); return $html; }
val1上でkeyupイベントが発生するとexported_funcが起動され、exported_func()にマッピングされたperl_func()がval1のvalueを引数にしてコールされます。perl_funcはval1のvalue(=$inputに格納)を「が入力されました」という文字列と結合して(=$outputに格納)、returnします。このリターンされた$outputがexported_func()の第2引数に指定されたresultdivが指し示すdivの値として返されます。
$cjx->build_html()はCGIオブジェクトと画面のHTMLを返すサブルーチンへのリファレンスを引数にとって、Ajaxコードを組み込んだHTMLの文字列を返します。
複数の入力に複数の出力 †
onClick="JSFUNC(['source1','source2'], ['dest1','dest2']);"
のような複数の入出力に対応するには、Perlのサブルーチン(PERLFUNC)で複数の値を引数で受け取り、配列で値を返せばOKです。
sub perl_func { my $source1 = shift; my $source2 = shift; my $output1 = "source1に" . $source1 . "が入力されました"; my $output2 = "source2に" . $source2 . "が入力されました"; Jcode::convert(\$output1, 'utf8', 'euc'); Jcode::convert(\$output2, 'utf8', 'euc'); return ($output1, $output2); }
例えば、上のようにサブルーチンを定義すると、$source1、$source2にそれぞれsource1、source2での入力値が格納され、$output1の値がdest1に、$output2の値がdest2に返されます。
aaaa
独自のJavaScriptファンクションにPerlサブルーチン(PERLFUNC)の返り値を渡す †
独自に定義したJavaScriptファンクションにPerlサブルーチン(PERLFUNC)の返り値を渡すこともできます。
onClick="exported_func(['input1'],[js_process_func]);"
上のjs_process_funcが返り値を渡すJavaScriptファンクション名です。idを指定する時と異なり、'で括りません。('で括るとidとみなしてCGI::Ajaxは動作します)
Perlサブルーチン(PERLFUNC)が複数値(=配列)を返す場合はjs_process_func()の方でargumentsオブジェクトを利用して取得してあげる必要があります。
function js_process_func () { var input1 = arguments[0]; var input2 = arguments[1]; // 自分でgetElementByIdで出力先を特定して値をセットする必要アリ document.getElementById('outputdiv').innerHTML = input1 + 'と' + input2; }
外部のスクリプトにリクエストする †
外部のスクリプトにAjaxのリクエストを飛ばして実行させることも可能です。その為には、CGI::Ajaxのコンストラクタに指定するマッピングでPerlサブルーチンリファレンスではなく、対象スクリプトのURLを指定します。
my $url = 'scripts/other_script.pl'; my $cjx = new CGI::Ajax( 'external' => $url );
ページ側の記述はこれまでと同様です。
onClick="external(['input1','input2'],['resultdiv']);"
外部スクリプト(scripts/other_script.pl)ではCGIオブジェクトのparamメソッドからargsキーを用いてinput1、input2の入力を取得できます。
#!/usr/bin/perl # scripts/other_script.plの例 use strict; use CGI; my $cgi = new CGI; my @input = $cgi->param('args'); print $cgi->header(); print "input length is " . @input . " . \n";
仮に上のスクリプトをscripts/other_script.plとすると、実行結果としてinput1、input2が共に入力されていれば「input length is 2 .」という文字列がresultdivに返されます。
GET / POST の切り替え †
非同期通信を実行する時にリクエストに使うMethodをGET / POSTから選ぶことができます。
GETを使う時は
onClick="exported_func(['input1'],['result1'], 'GET');"
POSTを使う時は
onClick="exported_func(['input1'],['result1'], 'POST');"
というふうにJavaScriptファンクションの第3引数に指定します。
デフォルトではGETが使われます。
感想 †
JavaScriptファンクションへの引数表現によってPerlサブルーチンにシームレスにinputタグの入力値を引き渡せるのは便利。サブルーチンの戻り値がそのままJavaScriptファンクションのアウトプットに指定したHTMLタグに返されるのも直感的でわかりやすいと感じました。
一番嬉しいのは、『Perlサブルーチン』にイベントをマッピングできることです。非同期に入力チェックを行うようなAjaxを考える場合に、入力チェックのみ行うようなスクリプトをわざわざ用意しなくてすむのはイイです。サブルーチンを用意するだけでいいんだから凄く便利。大抵のレガシーでは入力チェックのロジックは既にサブルーチン化されてライブラリにあったりするわけで、サブルーチンにマップできるということはこの資産を楽に利用できることになりますよね。
心配なのは今後の対応と発展でしょうか。prototype.jsが隆盛の昨今、prototype.jsとまったくかすらないライブラリの利用は少し不安にはなります^^;