at posts/single.html

習作

最近注目を浴びている Ajax という技術がある。 Ajax は JavaScript (とXMLHttpRequest)を使って裏で通信を行い、動的にページを書き換えるというもの。 概要については、「はてなダイアリーのキーワード」や、「〜JavaScriptでXMLHttpRequest〜」が参考になる。

んで、習作として Ajax を使ったチャットを作ってみた。 Ajax を使う利点としては、フレームを使わずにログの自動リロードができるところか。 Ajax を使った 日本語 IMEなどの素晴らしいサンプルに比べるとインパクトは小さいけど、一つの例になればと思う。

以下、作成する上でポイントと思った箇所をメモ。

XMLHttpRequest の生成

Ajax のコアとなるコンポーネントが XMLHttpRequest で、Web ブラウザと同じように、特定リソースへの GET や PUT POST ができる。 まずはこの XMLHttpRequest を生成しないといけないんだけど、生成する方法は Web ブラウザによって異なるみたい。

この違いを吸収してくれるグッドラッパーが、Cross-Browser XMLHttpRequest - Web Site Designで xmlhttprequest.js として公開されている(ライセンスは Creative Commons License )。 このスクリプトを読み込むことで、 Firefox でも IE でも

var xmlhttp = new XMLHttpRequest();

と書くだけで XMLHttpRequest を生成できるようになる。

データの取得 (GET)

まずは、 XMLHttpRequest を使ってデータを取得するサンプルを作成。

<html>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP">
<script type="text/javascript" src="xmlhttprequest.js"></script>
<script type="text/javascript">
<!--
function load() {
  xmlhttp = new XMLHttpRequest();
  if (!xmlhttp) {
    alert('Sorry, cannot use XMLHttpRequest');
    return;
  }

  xmlhttp.onreadystatechange = function() {
    // データを受信したら以下の処理を実行
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      var disp = document.getElementById('disp');
      disp.innerHTML = xmlhttp.responseText;
    }
  }
  xmlhttp.open('GET', 'data.html', true);
  xmlhttp.setRequestHeader("If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");
  xmlhttp.send("");
}
// -->
</script>
<body>
<div><button onclick="load()">読み込み</button></div>
<div id="disp" style="border: dashed 1px #888; padding: 0.5em"></div>
</body></html>

基本的は先ほど紹介した「〜JavaScriptでXMLHttpRequest〜と同じ。 この HTML ファイルを EUC-JP で作成し、同じ場所に xmlhttprequest.js と data.html を置いておく。 data.html は、以下のような HTML の断片。

<p>こんにちは</p>

これで、「読み込み」ボタンを押すと点線で囲まれた領域に data.html の内容が表示される。

ポイントは…

xmlhttp.open('GET', 'data.html', true);

GET メソッドを使って data.html を取得するように指定する(実際にデータを取得するのは、send メソッドが呼ばれたあと)。 第三引数は同期・非同期通信の指定。 true を指定すると非同期通信(データの取得が完了する前に処理が進む)で、 false を指定すると同期通信(データを取得するまで処理を待つ)になる。 同期通信にすると、データを取得するまでブラウザが固まるので注意。 詳しくは「Hawk's W3 Laboratory : XML : XMLHttpRequestについて」や「JavaScript++かも日記: Sync(同期)とAsync(非同期)の体感テスト」が参考になる。

xmlhttp.setRequestHeader("If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");

GET リクエスト時に If-Modified-Since を付与するように指定している。 IE の場合、このヘッダを指定しないとブラウザのキャッシュを見にいってしまうようだ。 (data.html の内容を変更しても、ブラウザに反映されない) このサンプルでは、値を 1970年 に設定しているけど、実際のアプリではサーバの負荷を考慮して、適切に値を指定するべきだろうね。 詳しくは「msanolog: XMLHttpRequest と If-Modified-Sinceが参考になる。

xmlhttp.send("");

send メソッドが呼ばれた時点で、実際に HTTP リクエストが発行される。 POST の場合、引数に送りたいデータを指定する。

xmlhttp.send(null)

との記述もよく見かけるけど、この記述だと Linux の Konqueror/3.3 でエラーになるとのこと。 「JavaScript++かも日記: send(null)でk3.3がエラー」より。

if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
  document.getElementById('disp').innerHTML = xmlhttp.responseText;
}

データを取得した後の処理はこれ以降に記述する。

status には HTTP のステータスコードが格納される。 ここでは、ステータスコードが 200 (OK) の場合に処理を続けるようにしている。 もし、 data.html が存在しない場合は 404 (Not Found) が返るので、この箇所は実行されない。

readyState には通信状態が格納される。4 は通信完了を示す値。通信途中に何らかの処理を行いたい場合は、他の値を指定するみたい。

これは「レスポンスのタイミングについての調査」が参考になる。 ここを見ると、 Opera はこの記述じゃだめっぽい…。

データを取得した後は、 getElementById と innerHTML を使って、 disp の領域に data.html の内容を格納している。 取得したデータは、 responseText や responseXML を使って取り出すことができる。 元のデータは UTF-8 でないと文字化けする。UTF-8 から EUC-JP への変換は JavaScript 側で自動的に処理してくれる。

JavaScript++かも日記: responseされる文字コードの調査によると、Opera 8.0 や Safari だと UTF-8 でも文字化けする模様。 responseXML を使う方が安全らしいけど、これについてはまた次回に。

JSON形式かつマルチバイト文字は\uXXXX形式でエンコードが一番確実っぽいけど、手間は増えるよなぁ…。

ここまでのまとめ

  • XMLHttpRequest の生成にCross-Browser XMLHttpRequest - Web Site Designを使用。
  • XMLHttpRequest#open の第三引数に true を指定する。
  • GET 時は XMLHttpRequest#send の引数に null ではなく "" を指定する。
  • XMLHttpRequest#setRequestHeader で If-Modified-Since を指定する。
  • responseText, responseXML は文字化けに注意。responseXML で UTF-8 が安定か。

独学なので間違いがあるかもしれないので注意。