以前から、このサイトのファイルフォーマット変換サービスで、1ファイル処理する毎に画面が切り替わってしまうのが不便で、ダウンロード画面だけを挿入したいものだと考えていた。Ajaxでの実装が結構簡単にできそうだったので、やってみることにした。
Ajaxを使うために必要なこと
きっかけは、先週、ソフトバンクのモバイルウィジェットで簡単なプロトタイプを作る話があったこと。ちょこちょこといじってみたら、Ajaxは意外と何とか使えるようになりそうな手応え。今迄は幾つかの問題から敬遠してきた: (イ)何やら難しそうだったこと、(ロ)ブラウザのサポート状況や互換性に問題がありそうだったこと、(ハ)ユーザがJavascript動作設定を切っている危険性。(ポップアップが沢山出たりするような悪用のために私も長らく切っていた)。でも、今はJavascriptが動かないと画面が見られないのも普通だし、これらの問題は解決されているようだ。
さて、世の中には正確だが難しい解説がいくらでもあるので、アバウトな説明に終始したい。プログラマがJavascript/Ajaxを使用するため(理解するためではない)には、以下を押さえれば良いと思う。
- 文法は概ねJavaと同じ。関数は後定義が可能。
- HTML文書には名前でアクセスする。特定するためには"id"、"name"を使い、汎用のタグ名でアクセスする場合には、出てくる順番に配列になっている。かなりアバウト。
- 関数、変数のスコープは原則文書内グローバル。(スクリプトっぽい)。関数内部変数は接頭語"var"を付けて宣言しておく。
- Ajaxでは単発のHTTP通信をでき、契機はボタン、マウスなどのイベントのフック、受信処理はコールバック関数。
- Firefox, IE, Opera, Safariで動作する。IEは6.0以降(WIndows XP以降)なら問題なし。
- FirefoxとIE8ではデバッグもできるし、記述もアバウトで大丈夫なようで、良きに計らってくれる。IE6、IE7はエラーに厳しくて間違いがあるとそれ以降の処理を行なわいようだ。デバッグもできないので、特に未定義の変数や関数、nullオブジェクト内の参照には細心の注意が必要である。
今回作ったYouTubeダウンローダページとスクリプトの解説
ユーザがフォーム入力したものを処理した結果を、同じページ内にインラインで挿入するものを作る。まずは、YouTubeのダウンローダであるが、基本的な手順は以下の通り、
ページの上のほうでJavascriptの関数を定義しておく。(ここでは別ファイルにしたが、埋め込みでも可)
<script type="text/javascript" src="ytdownloader.js"></script>
ページ内のフォームの上に見えないブロックを作る。ここに処理結果を挿入する。idでアクセスするので、一意のIDを付けておく。
<div id="dld_result" style="display:none"></div>
フォームのsubmitから、Javascriptの関数を呼ぶようにする。
<form name="F1" action="javascript:YtDownloader();" method="get">
ページの一番下でAjax通信の初期化をする。
<script type="text/javascript">
Initialize();
</script>
中身はこんなもの。互換性のためにおまじないじみているが、定型の処理。最初の関数が標準的なもの)。成功したら、受信時のコールバック関数ポインタをセットしておく。
xmlhttp = null;
function Initialize()
{
if (window.XMLHttpRequest) {
try {
xmlhttp = new XMLHttpRequest();
} catch (e) {}
} else if (window.ActiveXObject) {
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
}
if (xmlhttp) {
xmlhttp.onreadystatechange = ReceiveData;
}
}
フォームがsubmitされたら、入力値を読んで、HTTPリクエストを送る。自動化されている通常のformと違い、入力値は自力で読んで、リクエストメッセージを組み立てないとならない。(なので、POSTになる場合はかなり面倒くさい)。なお、ここでは折角なので、通信が戻ってくるまで、「お待ち下さい」を出すことにした。表示は、上で作った隠しブロック(id=dld_result)の中身にHTMLで書いて、見えるようにするためにstyle.displayのプロパティを"none"->"block"に変更すれば良い。
function YtDownloader()
if (!xmlhttp) {
return;
}
var ytid = null, eqmt = null, i;
// フォームの値を読む
ytid = escape(document.F1.youtubeid.value);
for (i = 0; i < 7; i++) {
if (document.F1.equipment[i].checked) {
eqmt = escape(document.F1.equipment[i].value);
break;
}
}
if (!ytid || !eqmt) {
return;
}
var url = "cgi-bin/ytdwnlderi.cgi?youtubeid=" + ytid + "&equipment=" + eqmt;
// Pstatus("YtDownloader: url = " + url);
// 通信を送る
xmlhttp.open("GET", url, true);
xmlhttp.send(null);
// 通信中を表示する
var drst = document.getElementById("dld_result");
if (drst) {
drst.innerHTML = "<img src=\"waiting.gif\" alt=\"waiting\">
<span class=\"strong\">ダウンロード + 変換しています。
お待ち下さい!</span>";
drst.style.display = "block";
}
return;
}
後は、返事が戻ってきたらコールバックされる。中身の意味は未調査であるが、観察結果から"readyState"は順に1→2→3→4が返ってくる。また、4が返ったときには、statusが返ってきており、この中身はHTTPのエラーコードのようだ。したがって、4まで来て、status 200が返っていれば正常に終了しており、結果を読み出すことができる。
function ReceiveData()
{
if (!xmlhttp || xmlhttp.readyState != 4) {
// Pstatus("ReceiveData readyState: " + xmlhttp.readyState + ".");
return;
}
// Pstatus("ReceiveData: status: " + xmlhttp.status);
var drst = document.getElementById("dld_result");
if (xmlhttp.status == 200) {
// サーバからの返答に上書きする
drst.innerHTML = xmlhttp.responseText;
} else {
// エラーしました、を上書きする
// Pstatus("ReceiveData status: " + xmlhttp.status + ".");
drst.innerHTML = "<img src=\"error.png\" alt=\"error\">
<span class=\"strong\"> Server Error!: "
+ xmlhttp.status + "</span>";
}
if (drst.style.display != "block") {
drst.style.display = "block";
}
}
以上で処理は終り。フォームをsubmitすると、dld_resultブロックの位置に取得したファイルをダウンロードするブロックが表示される。
さてさて、一番困るのがデバッグだ。Firefoxの"firebug"、IE8の"開発者ツール"が使えれば充分であるが、テスト環境で素のままのXPなどでのデバッグはできない。最低限プリントデバッグはしたいものだ。consoleとかもあるが、古い環境ではこれを書くだけでエラーしてしまうので、簡単なプリントデバッグを作った。
まず、本文の一番下(フッターなど)にデバッグ出力のブロックを作っておく。
<div id="DebugLog" style="clear:both;display:none"></div>
ここに書く関数を定義しておく、
function Pstatus(str)
{
var dl = document.getElementById("DebugLog");
if (dl) {
if (dl.style.display == "none") {
dl.style.display = "block";
}
dl.innerHTML += str + "<br>";
} else {
alert("Pstatus: dl not found!");
}
// console.log(str + "<br>");
return true;
}