トップ «前の日記(2009年03月29日) 最新 次の日記(2009年04月10日)» 編集

Masa's blog

検索キーワード:

2009年03月31日 Ajax儂(わし)的解釈によるメモ [長年日記]

_ Ajax儂(わし)的解釈によるメモ

ウェブアプリを作る時はw3mでも支障無く動くことをモットーとしている。それゆえWeb2.0だとかAjaxというようなキーワードがもてはやされる時代になっても、かたくなに目を背けてやってきた。しかし、他人に「何故お前は(Web2.0||Ajax)を(やらん||できん)のだ」と問われた時に、明解に理由を説明できるだけの知識は持ち合わせていなかった。

ここいらで少しWeb2.0だとかAjaxという世界に寄り道してみようと思う。そう「毒をもって毒を制す」という言葉も在るではないか...。

※ 用語等の利用に関しては自分なりの解釈に元づいたものなので、あまり当てにしないでもらいたい...

とりあえずAsynchronousとJavascript

Ajax = Asynchronous JavaScript + XMLという事らしいのだが、XMLは後回しにして、とりあえずAsynchronousとJavaScriptの部分を探っていきたい。

通常のウェブアプリではユーザからのデータ入力は<form>タグを介して行われ、[submit]ボタンをクリックする事でサーバ側(CGIプログラム)に入力データが渡され、サーバ側で何らかの処理が行われた後に、その結果をhtmlドキュメント形式で表現しブラウザ側に返してやる。サーバ側で処理が行われている間はブラウザ側では一切の作業ができない(旗がゆらめいたり、何かがクルクル回ったりしているだけ)。

JavascriptからXMLHttpRequest()等のオブジェクトを作成して、これを利用すると非同期通信が可能になる。つまりブラウザからサーバへリクエストを送信した後、ブラウザはサーバからのレスポンスを待つこと無くユーザの次の入力要求に答える事ができる。それではサーバのレスポンスは何処へ行ってしまうのか?。

サーバからのレスポンスは「イベント」としてブラウザに通知される。せっかちなユーザの入力を受け付ける事に専念している最中に肩を叩かれる(イベントの到着)ので、その時だけデータを受け取りに行けば良い。

非同期通信にてサーバから返されるデータはhtmlドキュメントである必要はない。もし、たった1つの項目が欲しければ、たった1つの項目だけを返してもらえば良い。

だが待て、ブラウザはhtmlドキュメント(<html>〜</html>)を表示するものではなかったか。全体のリロード無しにどうやって表示を変えるのだ?。

さて、ブラウザ内でhtmlドキュメントはDOM(Document Object Model)という形式で保持されている。タグやら文字列の入れ子構造がそのままツリー構造として存在していると考えればあながち間違いでもなかろう。ツリーの葉っぱの部分は個々のタグや文字列ということか。

JavascriptからはDOMという木の個々の葉っぱ(だけ)を書き換える事ができる。この機能のおかげで、レスポンス到着イベントのタイミングでブラウザの表示の一部分だけを(全体のリロード無しで)更新する事ができる。

以上の事が、Ajax(のXML抜きの)本質部分なのではないかと思う。

Ajax対応Javascript(htmlファイル)サンプル

http://myh.no-ip.org/~m-ito/ajaxtest1.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=euc-jp">
<title>ajaxtest1</title>

<script type="text/javascript">
<!--
// ============================================================
//   私製Ajax関連ルーチン(ここから)
//
//     後々、別ファイルに独立させて共通ルーチンとして利用するつもり
//
// ============================================================
//
// 非同期通信オブジェクト作成
//
	function ajax_createXHR(){
		var xhr;
		try{
			xhr = new ActiveXObject("Msxml2.XMLHTTP");
		}catch(e){
			try{
				xhr = new ActiveXObject("Microsoft.XMLHTTP");
			}catch(e){
				try{
					xhr = new XMLHttpRequest();
				}catch(e){
					xhr = null;
				}
			}
		}
		return xhr;
	}

Ajaxの肝となる非同期通信オブジェクトの生成方法はブラウザ依存、バージョン依存が激しい。肝なのに。早速に不審感がつのる。とりあえず3種類の方法を試していって、成功すればそのオブジェクトを返すといった感じ。

//
// idで指定したエレメントを取得する
//
	function ajax_gebi(id){
		return document.getElementById(id);
	}
//
// tagで指定したエレメントの内、idx番目(0オリジン)の物を取得する
//   ただし、idx < 0の場合はgetElementsByTagName(tag)を取得する
//
	function ajax_gebt(tag, idx){
		if (idx < 0){
			return document.getElementsByTagName(tag);
		}else{
			return document.getElementsByTagName(tag).item(idx);
		}
	}
//
// name指定したエレメントの内、idx番目(0オリジン)の物を取得する
//   ただし、idx < 0の場合はgetElementsByName(name)を取得する
//
	function ajax_gebn(name, idx){
		if (idx < 0){
			return document.getElementsByName(name);
		}else{
			return document.getElementsByName(name).item(idx);
		}
	}
//
// idで指定したエレメント配下の子エレメントを全て削除
//
	function ajax_removeChilds(id){
		var max = document.getElementById(id).childNodes.length;
		for (var i = 0; i < max; i++){
			document.getElementById(id).removeChild(document.getElementById(id).childNodes.item(0));
		}
	}
//
// idで指定したエレメント配下の子エレメントをstrで指定したテキストエレメントで置換
//
//   o 単純に文字列表示として利用することを想定している
//   o idで指定するエレメントは<span>であることを想定している
//
	function ajax_setText(id, str){
		ajax_removeChilds(id);
		document.getElementById(id).appendChild(document.createTextNode(str));
	}
//
// idで指定したエレメント配下のテキストエレメントの文字列を取得
//
	function ajax_getText(id){
		var text = "";
		for (var i = 0; i < document.getElementById(id).childNodes.length; i++){
			if (document.getElementById(id).childNodes.item(i).nodeValue){
				text += document.getElementById(id).childNodes.item(i).nodeValue;
			}
		}
		return text;
	}
//
// idで指定したエレメントの値にstrで指定した文字列をセットする
//
//   o idで指定するエレメントは<input>等のvalueを持つものであることを想定している
//
	function ajax_setValue(id, str){
		document.getElementById(id).value = str;
	}
//
// idで指定したエレメントの値を取得する
//
//   o idで指定するエレメントは<input>等のvalueを持つものであることを想定している
//
	function ajax_getValue(id){
		return 	document.getElementById(id).value;
	}

とりあえずJavascriptのプログラミングは記述が長い。まぁオブジェクト、メソッド、プロパティ...等々をドットで繋げて行くスタイルをとるオブジェクト指向プログラミング言語全般に言える事かもしれないが。手が疲れるので適当なラッパールーチンを用意する。

//
// idで指定したエレメントの値を送信できる形式に変換する
//
//   o idで指定するエレメントは<input>等のvalueを持つものであることを想定している
//
	function ajax_encodedValue(id){
		return encodeURIComponent(document.getElementById(id).value);
	}
//
// idで指定したエレメントの値を通常の文字列に戻す
//
//   o idで指定するエレメントは<input>等のvalueを持つものであることを想定している
//
	function ajax_decodedValue(id){
		return decodeURIComponent(document.getElementById(id).value);
	}
//
// strで指定した文字列を送信できる形式に変換する
//
	function ajax_encodedString(str){
		return encodeURIComponent(str);
	}
//
// strで指定した文字列を通常の文字列に戻す
//
	function ajax_decodedString(str){
		return decodeURIComponent(str);
	}

データはエンコードして送信する。エンコード処理にも色々あるがencodeURIComponentdecodeURIComponentの具合いが良さそう。漢字データも`?', `&', `=', `\n'等を含んだデータのやりとりもOK。また、エンコードした際に文字コードはContent-type,charsetの指定とは無関係にUTF-8に変換される。つまり、非同期通信オブジェクトを介した通信ではUTF-8が強制されるらしい。

//
// CGIからのレスポンスをハッシュに取り込む
//
//  o レスポンス形式 : 項目名=値\t項目名=値\t ... \t項目名=値
//
	function ajax_getResponse(xhr){
		var ary = xhr.responseText.split("\t");
		var kv = new Array();
		var res = new Array();
		var i = 0;
		while (i < ary.length){
			kv = ary[i].split("=");
			res[kv[0]] = decodeURIComponent(kv[1]);
			i++;
		}
		return res;
	}

サーバ(CGI)からのレスポンスの形式にはXMLJSONといった形式が使われる事もあるが、とりあえずシンプルな(使い慣れた)テキスト形式(項目名=値\t項目名=値\t ... \t項目名=値)を使う。項目名がハッシュのインデックスとなる配列を返す。

//
// 受信完了状態取得
//
	function ajax_getRecvStatus(xhr){
		if (xhr.readyState == 4){
			if (xhr.status == 200){
				return "OK";
			}else{
				return "NG";
			}
		}else{
			return "NOTREADY";
		}
	}

レスポンス受信イベント(正確には状態変化のイベント)発生の状態を取得する。`OK'(正常の受信完了), `NG'(受信失敗), `NOTREADY'(受信中)の3つのステータスを返す。

//
// リクエスト送信(および受信ハンドラ設定)
//
	function ajax_sendRequest(uri, msghash, async, callbackOK, callbackNG, callbackNR){
		var xhr = ajax_createXHR();
		xhr.onreadystatechange = function(){
			var status = ajax_getRecvStatus(xhr);
			if (status == "OK"){
				if (!(callbackOK == null || callbackOK == "")){
					callbackOK(xhr);
				}
			}else if (status == "NG"){
				if (!(callbackNG == null || callbackNG == "")){
					callbackNG(xhr);
				}
			}else{
				if (!(callbackNR == null || callbackNR == "")){
					callbackNR(xhr);
				}
			}
		}
		var msg = "";
		i = 0;
		for (i in msghash){
			if (i == 0){
				msg = i + '=' + ajax_encodedString(msghash[i]);
			}else{
				msg = msg + '&' + i + '=' + ajax_encodedString(msghash[i]);
			}
			i++;
		}
		xhr.open("POST", uri + "?dummy=" + new Date().getTime(), async);
		xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		xhr.send(msg);
		return xhr;
	}

(ついに)リクエストを送信すると同時に受信時のコールバックを登録する。

  • uriはCGIへのパス
  • msgはCGIパラメータとして標準的な形式(項目名=値&項目名=値& ... &項目名=値)
  • asyncは非同期通信フラグ(true | false)
  • callbackOKはステータス`OK'時の処理関数
  • callbackNGはステータス`NG'時の処理関数
  • callbackNRはステータス`NOTREADY'時の処理関数

uri に "?dummy=" + new Date().getTime()を追加してopenしているのはキャッシュの影響でデータ更新が正常に機能しない場合の対応の為。

//
// テキスト入力エレメント内のキャレット位置を取得する
//
	function ajax_getCaretPos(elm){
		var pos;

		if (elm.selectionStart >= 0){
			elm.selectionEnd = elm.selectionStart;
			pos = elm.selectionStart;
		}else if (document.selection.createRange){
//
// http://d.hatena.ne.jp/language_and_engineering/20090225/p1
//
			elm.focus();	// for Opera
// ボックスの先頭からキャレットまでのrangeを作って,長さを調査
			var range = document.selection.createRange();
			range.moveStart( "character", -elm.value.length );
			pos = range.text.length;
		}

		return pos;
	}

ウェブアプリというとユーザインターフェースが貧弱と思われがち。ブラウザと<form>タグの組合せにまかせっきりになりがちなので。Javascript + DOMを活用するとかなり使い勝手をあげる事ができる。が、やはりブラウザ依存な部分が多い。

//
// [↑][↓][←][→]のイベントにより入力フォーカスを移動させる
//
	function ajax_moveFocus(evt, upId, downId, leftId, rightId){
		var target = evt.target || evt.srcElement;
		if (evt.keyCode == 37){
			if (ajax_getCaretPos(target) == 0){
				document.getElementById(leftId).focus();
				return false;
			}
		}else if (evt.keyCode == 38){
			document.getElementById(upId).focus();
		}else if (evt.keyCode == 39){
			if (ajax_getCaretPos(target) >= target.value.length){
				document.getElementById(rightId).focus();
				return false;
			}
		}else if (evt.keyCode == 40){
			document.getElementById(downId).focus();
		}
		return true;
	}

汎用機(メインフレーム)的なオンライン画面と比べるとウェブアプリって、入力項目の移動がしにくいと感じる。カーソルキーで上下左右に自由に入力項目間の移動ができないとストレスがたまる(ような気がする)。と言いながらも他人にウェブアプリの操作を説明する時には「ウェブアプリとはそういうもんです」と説明してきた自分がいるわけだが、やれば出来るもんだねぇ。ちょっぴりJavascriptが素敵に思えた。

// ============================================================
//   私製Ajax関連ルーチン(ここまで)
// ============================================================
//
//

ここまでのルーチンは別ファイルに保存して、汎用ルーチンとして利用するのが良いかと思う。まぁ、そういう汎用ルーチンは既にいっぱい存在するわけだけれども、どれをとっても自分にはオーバースペックであり、かつ全体を把握しきれないものを使う気にはなれない。

//
// ============================================================
//   ユーザコーディング(ここから)
// ============================================================
// ------------------------------------------------------------
//   非同期リクエストによるテキスト電文通信
// ------------------------------------------------------------
//
//  送信イベントのコールバック
//
//    o 入力データ等から送信メッセージを組み立てる。
//    o ajax_sendRequest()を発行する。
//
	var Xhr_send_msg = "";
	function send_msg(){
		var uri = "cgi-bin/ajaxtest1.cgi";
		var msg = {'req':'req1',
				'tx1':ajax_getValue("tx1"),
				'in1':ajax_getValue("in1"),
				'in2':ajax_getValue("in2")
		}
		var async = true;
		var callbackOK = recv_ok;
		var callbackNG = recv_ng;
		var callbackNR = null;	// 受信が完了してなかった場合のコールバック
		Xhr_send_msg = ajax_sendRequest(uri, msg, async, callbackOK, callbackNG, callbackNR);
	}
//
// OK受信イベントのコールバック
//
//   o 引数にメッセージリクエストオブジェクト(xhr)を指定する。
//   o ajax_getResponse()でメッセージデータを取得する。
//   o メッセージデータを処理する。
//
	function recv_ok(xhr){
		res = ajax_getResponse(xhr);
		ajax_setValue("res_tx1", res["res_tx1"]);	// textareaタグに表示
		ajax_setText("res_in1", res["res_in1"]);	// テキストの表示
		ajax_setText("res_in2", res["res_in2"]);	// テキストの表示
		ajax_gebi("res_in1_font").color = "red";		// タグのパラメータ設定
		ajax_gebt("font", 1).color = "blue";		// タグのパラメータ設定
	}
//
// NG受信イベントのコールバック
//
//   o 引数にメッセージリクエストオブジェクト(xhr)を指定する。
//   o 受信に失敗した場合の処理をする。
//
	function recv_ng(xhr){
		ajax_setText("res1", "Error1");
		ajax_setText("res2", "Error2");
		ajax_setValue("res3", "Error3");
	}

ここまでは非同期通信による送受信のサンプル。

// ------------------------------------------------------------
//   インターバルリクエストによる定期的な表示更新
// ------------------------------------------------------------
//
// インターバル・送信イベントのコールバック
//
	var Xhr_send_psmsg = "";
	var tid = setInterval("send_psmsg()",5000);
	function send_psmsg(){
		var uri = "cgi-bin/ajaxtest1.cgi";
		var msg = {'req':'req2',
				'option':'x'
		}
		var async = true;
		var callbackOK = recv_psok;
		var callbackNG = recv_psng;
		var callbackNR = null;
		Xhr_send_psmsg = ajax_sendRequest(uri, msg, async, callbackOK, callbackNG, callbackNR);
	}
//
// インターバル・OK受信イベントのコールバック
//
	function recv_psok(xhr){
		res = ajax_getResponse(xhr);
		ajax_setText("ps", res["ps"]);
	}
//
// インターバル・NG受信イベントのコールバック
//
	function recv_psng(xhr){
		ajax_setText("ps", "Error5");
	}

ここまでは、インターバルリクエストを利用して、定期的に非同期通信を行うサンプル

// ------------------------------------------------------------
//   キー入力イベントごとのKeyCodeを表示 etc ...
//      Ajaxではないな...javascriptのサンプル
// ------------------------------------------------------------
//
// 入力キーコードを表示
//
	function keyget(evt, displayId){
		ajax_setText(displayId, "KeyCode=" + evt.keyCode);
	}
// ------------------------------------------------------------
//   ラジオボタンの値取得
// ------------------------------------------------------------
	function get_radio(){
		for (var i = 0; i < ajax_gebn("rd1", -1).length; i++){
			if (ajax_gebn("rd1", i).checked){
				ajax_setText("rd", ajax_gebn("rd1", i).value);
				break;
			}
		}
	}
// ------------------------------------------------------------
//   チェックボックスの値取得
// ------------------------------------------------------------
	function get_check(){
		var checked_value = "";
		if (ajax_gebi("ck1").checked){
			checked_value += ajax_gebi("ck1").value;
		}
		if (ajax_gebi("ck2").checked){
			if (checked_value != ""){
				checked_value += "+";
			}
			checked_value += ajax_gebi("ck2").value;
		}
		if (ajax_gebi("ck3").checked){
			if (checked_value != ""){
				checked_value += "+";
			}
			checked_value += ajax_gebi("ck3").value;
		}
		ajax_setText("ck", checked_value);
	}
// ------------------------------------------------------------
//   セレクトボックスの値取得
// ------------------------------------------------------------
	function get_select(){
		ajax_setText("sl", ajax_gebi("sl1").options.item(ajax_gebi("sl1").selectedIndex).value);
	}

各種入力エレメントからの値取得のサンプル。

// ------------------------------------------------------------
//   表示/非表示の制御
// ------------------------------------------------------------
	function toggle_display(){
		if (ajax_gebi("div_display").style.display == "none"){
			ajax_gebi("div_display").style.display = "";
		}else{
			ajax_gebi("div_display").style.display = "none";
		}
	}
// ============================================================
//   ユーザコーディング(ここまで)
// ============================================================
// -->
</script>

スタイルの制御サンプル。

</head>
<body>
<div align="center">
<h1>
ajaxtest1
</h1>
</div>

<hr /> <!-- ------------------------------------------------------------ -->

<h2>
非同期リクエストによるテキスト電文通信
</h2>
+ちょっとだけDOMの操作(なんせ、これがうまれて初めてのAjaxコーディング...)
<div>
tx1:<textarea id="tx1"></textarea>
</div>
<div>
in1:<input type="text" id="in1" name="in1"><br />
in2:<input type="text" id="in2" name="in2" onblur="send_msg()"> ←ここからフォーカスが外れたらメッセージ送信する
</div>
<p></p>
<div>
res_tx1(tx1に対するレスポンス):[<textarea id="res_tx1" name="res_tx1"></textarea>]
</div>
<div>
res_in1(in1に対するレスポンス):[<font id="res_in1_font"><span id="res_in1" name="res_in1"></span></font>]
</div>
<div>
res_in2(in2に対するレスポンス):[<font id="res_in2_font"><span id="res_in2" name="res_in2"></span></font>]
</div>
<hr /> <!-- ------------------------------------------------------------ -->

<h2>
インターバルリクエストによる定期的な表示更新
</h2>
<div>
<pre id="ps">
</pre>
</div>
<div>
<input type="button" onclick="clearInterval(tid);" value="クリックで停止">
</div>
<hr /> <!-- ------------------------------------------------------------ -->

<h2>
ラジオボタンの値取得
</h2>
<div>
<input type="radio" id="rd1-1" name="rd1" value="radio1" checked>選択肢1<br />
<input type="radio" id="rd1-2" name="rd1" value="radio2">選択肢2<br />
<input type="radio" id="rd1-3" name="rd1" value="radio3">選択肢3<br />
<input type="button" value="OK" onclick="get_radio()">
</div>
選択されたのは[<span id="rd"></span>]
<hr /> <!-- ------------------------------------------------------------ -->

<h2>
チェックボックスの値取得
</h2>
<div>
<input type="checkbox" id="ck1" name="ck1" value="check1">選択肢1<br />
<input type="checkbox" id="ck2" name="ck2" value="check2">選択肢2<br />
<input type="checkbox" id="ck3" name="ck3" value="check3">選択肢3<br />
<input type="button" value="OK" onclick="get_check()">
</div>
選択されたのは[<span id="ck"></span>]
<hr /> <!-- ------------------------------------------------------------ -->

<h2>
セレクトボックスの値取得
</h2>
<div>
<select id="sl1">
<option value="none" selected>
<option value="select1">選択肢1
<option value="select2">選択肢2
<option value="select3">選択肢3
</select>
<input type="button" value="OK" onclick="get_select()">
 </div>
選択されたのは[<span id="sl"></span>]
<hr /> <!-- ------------------------------------------------------------ -->

<h2>
表示/非表示の制御
</h2>
<div id="div_display">
出て来たり〜消えたり〜
</div>
<input type="button" value="表示/非表示" onclick="toggle_display()">
<hr /> <!-- ------------------------------------------------------------ -->

<h2>
キー入力イベントごとのKeyCodeを表示
</h2>
<div>
<input type="text" onkeydown="keyget(event, 'kd1');">onKeyDown : <span id="kd1"></span>
</div>
<div>
<input type="text" onkeyup="keyget(event, 'ku1');">onKeyUp : <span id="ku1"></span>
</div>
<div>
<input type="text" onkeypress="keyget(event, 'kp1');">onKeyPress : <span id="kp1"></span>
</div>

<h2>
[↑][↓][←][→]のイベントにより入力フォーカスを移動させる
</h2>
<div>
<input type="text" id="in11" onkeydown="keyget(event, 'kd11');return ajax_moveFocus(event, 'in32', 'in21', 'in12', 'in12');">onKeyDown : <span id="kd11"></span>
<input type="text" id="in12" onkeydown="keyget(event, 'kd12');return ajax_moveFocus(event, 'in32', 'in22', 'in11', 'in11');">onKeyDown : <span id="kd12"></span>
</div>
<div>
<input type="text" id="in21" onkeydown="keyget(event, 'kd21');return ajax_moveFocus(event, 'in11', 'in31', 'in22', 'in22');">onKeyDown : <span id="kd21"></span>
<input type="text" id="in22" onkeydown="keyget(event, 'kd22');return ajax_moveFocus(event, 'in12', 'in32', 'in21', 'in21');">onKeyDown : <span id="kd22"></span>
</div>
<div>
<input type="text" id="in31" onkeydown="keyget(event, 'kd31');return ajax_moveFocus(event, 'in21', 'in11', 'in32', 'in32');">onKeyDown : <span id="kd31"></span>
<input type="text" id="in32" onkeydown="keyget(event, 'kd32');return ajax_moveFocus(event, 'in22', 'in12', 'in31', 'in31');">onKeyDown : <span id="kd32"></span>
</div>

</body>
</html>

ajax_moveFocus()はイベントに対してreturn ajax_moveFocus(...)のようにreturnを付けて指定するのがミソ。こうしないと[←][→]で入力フィールドを移動した時に移動先のフィールドでカーソルが余計に進んでしまう。returnをつけてイベントハンドラを呼び出すとイベントハンドラがfalseを返した場合に、そのイベントの本来の動作をキャンセルできる。

Ajax対応CGIサンプル(ruby)

#! /usr/local/bin/ruby
require "cgi"
require "uri"
require "nkf"
#============================================================
#
# Ajaxサポートクラス(あぁ楽しき哉、車輪の再発明)
#
#============================================================
class Ajax
#------------------------------------------------------------
#
# コンストラクタ
#
# 引数
#
#   nkf_param      : 入力パラメータの文字コード変換指定(for nkf)。省略可。
#   nkf_param_send : 送信パラメータの文字コード変換指定(for nkf)。省略可。
#
# 戻値 : 無し
#
#------------------------------------------------------------
	def initialize(nkf_param = "-X -e", nkf_param_send = "-w")
		@nkf_param = nkf_param
		@nkf_param_send = nkf_param_send
		@cgi = CGI.new("html3")
	end
#------------------------------------------------------------
#
# パラメータ取得
#
# 引数
#
#   name : 項目名称。省略可。
#
# 戻値
#
#   name指定時は、指定した項目の値を返す。
#   name未指定は、全パラメータの[項目名、値]のセットをイテレータで返す。
#
#------------------------------------------------------------
	def get(name = "")
		if (name == "")
			@cgi.each do |key, val|
				yield(key, NKF.nkf(@nkf_param, val))
			end
		else
			return NKF.nkf(@nkf_param, @cgi[name])
		end
	end
#------------------------------------------------------------
#
# レスポンス送信
#
# 引数
#
#   hash	 : 送信データ。ハッシュ形式。
#   boolean_head : HTTPヘッダーを送信(true)/非送信(false)の指定。省略可。
#   head_str     : HTTPヘッダーの内容。省略可。
#
# 戻値 : 無し
#
#------------------------------------------------------------
	def send(hash = {}, boolean_head = true, head_str = "Content-Type: text/html\n\n")
		if (boolean_head)
			print head_str
		end

		i = 0
		hash.each do |key, val|
			print "#{key}="
			print URI.escape(NKF.nkf(@nkf_param_send, val),/[^-_.!~*'()a-zA-Z\d]/n)
			i += 1
			if (i < hash.size)
				print "\t"
			end
		end
	end
end
#============================================================
#
# Main
#
#============================================================
ajax = Ajax.new

if (ajax.get("req") == "req1")
	in1 = ajax.get("in1")
	in2 = ajax.get("in2")
	tx1 = ajax.get("tx1")

	hash = {
		"res_tx1" => "response=(" + tx1 + ")",
		"res_in1" => "response=(" + in1 + ")",
		"res_in2" => "response=(" + in2 + ")"
	}

	ajax.send(hash)
elsif (ajax.get("req") == "req2")
	option = ajax.get("option")

	option.gsub!(/(.)/){'\\' + $1}
	cmdline = "date;ps #{option}"
	result = `#{cmdline}`
	result.gsub!(/$/, "\r")

	hash = {"ps" => result}

	ajax.send(hash)
end

exit 0

rubyにおけるJavascriptのencodeURIComponent()と等価の処理は URI.escape("hogehoge", /[^-_.!~*'()a-zA-Z\d]/n)となる。また、非同期通信オブジェクトに返すメッセージはUTF-8に変換してからエンコードする必要があることに注意。

Javascript + DOMによるhtmlドキュメントの作成サンプル

JavascriptからDOMを操作する事のみでhtmlドキュメントを作制してみる。

http://myh.no-ip.org/~m-ito/ajaxtest2.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=euc-jp">
<title>ajaxtest2</title>

<script type="text/javascript">
<!--
// ============================================================
//   ユーザコーディング(ここから)
// ============================================================
	window.onload = function(){
		var max = document.getElementsByTagName("body").item(0).childNodes.length;
		for (var i = 0; i < max; i++){
			document.getElementsByTagName("body").item(0).removeChild(document.getElementsByTagName("body").item(0).childNodes.item(0));
		}
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("div"));
		document.getElementsByTagName("div").item(0).align = "center";
		document.getElementsByTagName("div").item(0).appendChild(document.createElement("h1"));
		document.getElementsByTagName("h1").item(0).appendChild(document.createTextNode("javascript & DOMでウェブページを作る"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("h2"));
		document.getElementsByTagName("h2").item(0).appendChild(document.createTextNode("はじめに"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("p"));
		document.getElementsByTagName("p").item(0).appendChild(document.createTextNode("javascriptでDOM(Document Object Model)を操作すると動的にページを作ることができます。とはいえ結構面倒くさい作業になります。まぁ普通はこんなことはしないと思われます(^^;。"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("h2"));
		document.getElementsByTagName("h2").item(1).appendChild(document.createTextNode("document.getElementsByTagName"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("p"));
		document.getElementsByTagName("p").item(1).appendChild(document.createTextNode("document.getElementsByTagName(\"タグ名称\").item(番号)を使ってタグのエレメントを特定します。document.getElementsByTagName(\"タグ名称\").item(番号).appendChild(エレメント)を使ってタグに子どものエレメントを追加していきます。基本的にはこの作業を繰り返してドキュメント構造を組み立てていきます。"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("h2"));
		document.getElementsByTagName("h2").item(2).appendChild(document.createTextNode("document.createElement"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("p"));
		document.getElementsByTagName("p").item(2).appendChild(document.createTextNode("document.createElement(\"タグ名称\")を使ってタグ(エレメント)を作成します。作成されたエレメントはappendChildの引数に指定でき、ドキュメントの構成要素となります。"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("h2"));
		document.getElementsByTagName("h2").item(3).appendChild(document.createTextNode("document.createTextNode"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("p"));
		document.getElementsByTagName("p").item(3).appendChild(document.createTextNode("document.createTextNode(\"文字列\")を使ってテキスト(エレメント)を作成します。作成されたエレメントはappendChildの引数に指定でき、これもまたドキュメントの構成要素となります。また、表示された文字列は自動的にエスケープ処理されるのでXSS対策もなされた状態になります。この辺りがinnerHTMLを使ってお手軽に表示した場合との違いです。"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("h2"));
		document.getElementsByTagName("h2").item(4).appendChild(document.createTextNode("まとめ?"));
		document.getElementsByTagName("body").item(0).appendChild(document.createElement("p"));
		document.getElementsByTagName("p").item(4).appendChild(document.createTextNode("基本的にhtmlドキュメントは「タグ」と「テキスト」の織りなすものなので、上記の機能を組み合わせて利用するだけで作成することができます(!?)。"));
	}
// ============================================================
//   ユーザコーディング(ここまで)
// ============================================================
// -->
</script>

</head>
<body>
<div>
aaa
</div>
<div>
bbb
</div>
<div>
ccc
</div>
</body>
</html>

現時点での結論

Javascriptの言語仕様そのものは意外にも比較的受け入れ易いものだった。しかしJavascriptでのビジネスロジックまでの構築は抵抗がある。理由としては、

  • ブラウザ間での仕様の相違が大きい
  • 同一ブラウザ間であっても異なるバージョン間での仕様の相違が大きい
  • エラーメッセージ等の問題点を通知する仕組みが弱い(文法エラーはダンマリとなる事が多い)

のような事があげられる。とにかくデバッグしずらい。

とは言えAjax + Javascript + DOM (+ XML?)を使うと、格段にユーザインターフェースの利便性が向上するのは間違いないし、非常に魅力的ではある。個人的にはそういう用途に絞って利用してみようかと思う。