レベル: 中級 Jack Herrington (jherr@pobox.com), Editor-in-Chief, Code Generation Network
2007年 3月 06日 Ajax (Asynchronous JavaScript + XML) は確かに 2006年を賑わせた技術用語で、2007年も同じく、あるいはそれ以上に賑わせそうですが、実際のアプリケーションにはどのように影響するのでしょう。また、どの一般的なアーキテクチャー・パターンが Ajax アプリケーションで広く使用されているのでしょうか。この記事では、作業の基盤として使える Ajax に共通の 5 つのデザイン・パターンを紹介します。
Ajax は間違いなく、誰もが自らのサイトに結び付けたがる Web 2.0 の流行語です。しかし、この言葉は実際には何を意味するのでしょうか。また、エンジニアはアーキテクチャー・レベルでどのように Ajax をサイトに統合しているのでしょうか。この記事では Ajax の基本を説明し、Web 2.0 開発のベスト・プラクティスとなっている Ajax のデザイン・パターンを紹介します。
まず第一に、Ajax は DHTML (Dynamic HTML) や XMLHTTPRequest オブジェクトなどの一連の技術をひとまとめにした流行語でしかありません。DHTML は、ハイパーテキスト・マークアップ言語 (HTML)、JavaScript コード、そしてカスケーディング・スタイル・シート (CSS) という 3 つの要素の組み合わせです。Web ページで JavaScript コードを使用すると、ページを動的に変更してコンテンツを追加、削除、または変更できます。これがまさに、DHTML の動的な部分です。JavaScript コードは XMLHTTPRequest オブジェクトを使用して、ページがロードされた後にサーバーからのデータを要求します。
サーバーに対する即時データ要求、そしてそのデータを使用するためのページの変更という 2 つの要素の組み合わせは、Ajax と呼ばれるものの本質であり、Web 2.0 サイトの動的性質です。
けれども Ajax という言葉の意味を説明したところで、これらの機能が現実の世界でどのように使われているのか、そしてサイトではどのように使うべきなのかは伝わりません。Ajax の使用方法を理解するには、一連の単純なデザイン・パターンが必要です。「デザイン・パターン」という言葉に馴染みがない方のために説明すると、この言葉は同名タイトルの優れた著書に由来しています (「参考文献」を参照)。エンジニアが直面するさまざまな共通のタスクに対応した実装パターンを記載しているこの著書は、システムの設計方法に関するベスト・プラクティスだけでなく、エンジニアがコードについて語るときに使用できる用語についても説明しています。
この記事では、Ajax に共通の 5 つのデザイン・パターンを紹介します。これらのデザイン・パターンの違いは、サーバーからデータを取得する際に HTML、XML、JavaScript コードのどれを使用するかです。まずは、サーバーからの新しい HTML でページを更新する、最も簡単なパターンから紹介します。
パターン 1: HTML セグメントの置き換え
おそらくもっとも一般的な Ajax タスクは、サーバーに更新済み HTML を要求し、その HTML でページの一部を更新することでしょう。このタスクは、例えば株価情報の更新のように定期的に実行できます。あるいは検索要求に応答するなど、オンデマンドでの更新も可能です。
リスト 1 は、サーバーにページを要求し、そのコンテンツをページの本文にある <div> タグに配置するコードです。
リスト 1. Pat1_replace_div.html
<html>
<script>
var req = null;
function processReqChange() {
if (req.readyState == 4 && req.status == 200 ) {
var dobj = document.getElementById( 'htmlDiv' );
dobj.innerHTML = req.responseText;
}
}
function loadUrl( url ) {
if(window.XMLHttpRequest) {
try { req = new XMLHttpRequest();
} catch(e) { req = false; }
} else if(window.ActiveXObject) {
try { req = new ActiveXObject('Msxml2.XMLHTTP');
} catch(e) {
try { req = new ActiveXObject('Microsoft.XMLHTTP');
} catch(e) { req = false; }
} }
if(req) {
req.onreadystatechange = processReqChange;
req.open('GET', url, true);
req.send('');
}
}
var url = window.location.toString();
url = url.replace( /pat1_replace_div.html/, 'pat1_content.html' );
loadUrl( url );
</script>
<body>
Dynamic content is shown between here:<br/>
<div id="htmlDiv" style="border:1px solid black;padding:10px;">
</div>
And here.<br/>
</body>
</html>
|
リスト 2 に、上記のコードが要求対象としているコンテンツを示します。
リスト 2. Pat1_content.html
HTML encoded content goes here. |
このページを Firefox でロードすると、図 1 のような結果が表示されます。
図 1. <div> タグが置換されたページ
リスト 1 のコードに戻って説明すると、まず注目する点は loadUrl() 関数です。サーバーに対して URL を要求するこの関数は、XMLHTTPRequest オブジェクトを使用してサーバーに新規コンテンツを要求しています。この関数はまた、ブラウザーがコンテンツを受信すると呼び出されるコールバック関数 (この例では、processReqChange) も指定しています。
processReqChange 関数は、オブジェクトを調べて要求が完了したかどうかを確かめます。完了している場合は、ページの <div> タグに含まれる innerHTML を応答のテキストに設定します。
<div> タグを動的コンテンツのプレースホルダーとして使用する方法は、Ajax コードの定番です。これらのタグには (この例のように境界線などを追加しない限り) 可視の内容はありませんが、コンテンツの配置場所を示すマーカーとして役立ちます。エンジニアが置換可能なセグメントとして使用するタグには、後で説明するように <span> タグもあります。<div> タグと <span> タグの違いは、前者は (パラグラフのように) 改行を設定しますが、後者はインライン・テキストのセクションに輪郭線を付けるところです。
processReqChange 関数にいったん話を戻すと、この関数では status と readyState の両方の値をチェックすることが重要です。ブラウザーによっては、要求の完了時にのみこの関数を呼び出す場合もあれば、継続的にこの関数を呼び出して要求が実行中であることをコードに知らせる場合もあるからです。
タブ付き表示による変形
このパターンには、表示をタブ付形式で作成するという変形もあります。リスト 3 に、単純なタブ付き Ajax インターフェースを示します。
リスト 3. Pat1_tabs.html
<html>
<script>
var req = null;
function processReqChange() {
if (req.readyState == 4 && req.status == 200 ) {
var dobj = document.getElementById( 'tabDiv' );
dobj.innerHTML = req.responseText;
}
}
function loadUrl( tab ) {
var url = window.location.toString();
url = url.replace( /pat1_tabs.html/, tab );
...
}
function tab1() { loadUrl( 'pat1_tab1_content.html' ); }
function tab2() { loadUrl( 'pat1_tab2_content.html' ); }
tab1();
</script>
<body>
<a href="javascript: void tab1();">Tab 1<a>
<a href="javascript: void tab2();">Tab 2<a>
<div id="tabDiv" style="border:1px solid black;padding:10px;">
</div>
</body>
</html>
|
最初のタブのコンテンツは、リスト 4 のとおりです。
リスト 4. Pat1_tab1_content.html
2 番目のタブのコンテンツは、リスト 5 のとおりです。
リスト 5. Pat1_tab2_content.html
 | |
実際の環境でこのコードがどのように表示されるかを確認するには、pat1_tabs.html のオンライン・バージョンを参照してください。 .
|
|
ブラウザーでこのページを表示すると、最初のタブが図 2 のように表示されます。
図 2. 最初のタブのコンテンツ
2 番目のタブのリンクをクリックすると、ブラウザーが 2 番目のタグのコンテンツを取得し、図 3 のようにタブ・エリアに表示します。
図 3. 2 番目のタブのコンテンツ
ユーザーからの要求を受けて、新たなコンテンツで表示の一部を更新するというのは (この例では、タブ付き表示のように錯覚させています)、このデザイン・パターンの真髄を表す使い方です。アプリケーション側からすると、カスタマーがダウンロードするページを大幅に軽量化することが可能で、カスタマーがオンデマンドでコンテンツにアクセスできるという利点があります。
Ajax が登場する以前の一般的な手法では、ページに最初のタブと 2 番目のタブを両方持たせ、要求に応じてタブの表示と非表示を切り替えることになります。つまり、2 番目のタブは決して表示されることがないとしても作成されるため、サーバーの時間と帯域幅がどちらも無駄になります。この新しい Ajax の手法を使えば、2 番目のタブはユーザーが要求したときにだけ作成されるようになります。
「続きを読む」の変形
HTML の置き換えに関しては、図 4 に示す「続きを読む」(Read more) バージョンもあります。
図 4. 私の退屈なブログ・エントリーの Read more リンク
例えば、この犬の散歩でこの後、どんなことが起こったのか読みたいとします。その場合、Read more リンクをクリックすると、リンクは図 5 のように完全に夢中にさせるストーリーに置き換わります。
図 5. Read more リンクをクリックした後のページ
カスタマーにとって有難いのは、ページを更新しなくてもシームレスに詳細な内容が表示されるという点です。
リスト 6 に、このページのコードを示します。
リスト 6. pat1_readmore.html
<html>
<script>
var req = null;
function processReqChange() {
if (req.readyState == 4 && req.status == 200 ) {
var dobj = document.getElementById( "moreSpan" );
dobj.innerHTML = req.responseText;
}
}
function loadUrl( url ) { ... }
function getMore()
{
var url = window.location.toString();
url = url.replace( /pat1_readmore.html/, 'pat1_readmore_content.html' );
loadUrl( url );
}
</script>
<body>
<h1>Walking the dog</h1>
I took my dog for a walk today.
<span id="moreSpan">
<a href="javascript: void getMore()">Read more...</a>
</span>
</body>
</html>
|
リスト 7 は、Read more セクションのコンテンツです。
リスト 7. pat1_readmore_content.html
It was a nice day out. Warm and sunny. My dog liked getting out for a stretch.
|
このコードでは、<div> タグの代わりに <span> タグを使用する方法を示しています。どちらのタグを使用するかはユーザー・インターフェース (UI) の要件によって異なりますが、コードを見るとわかるように、いずれのタグも簡単に使用できます。
ページに新しい HTML を取得する方法は別として、ページでデータを使って実際にインテリジェントなことを行うために JavaScript コードが必要な場合についてはどうでしょう。データを体系的にブラウザーに渡すには何を使えばいいのでしょうか。その答えはもちろん、XML です。
パターン 2: XML データの読み取り
どういうわけか、Ajax は XML の代名詞となっていますが、Ajax には XML が絶対的に必要というわけではありません。上記の例からわかるように、Ajax ではそのままのテキストや HTML (または XHTML) コードのフラグメントでも返すことができます。しかし、XML を送信すると、それに見合った良い点があります。
リスト 8 に示すのは、本に関するレコードをサーバーに要求し、そのデータをページ内の表に表示する Ajax コードです。
リスト 8. pat2_xml.html
<html>
<head>
<script>
var req = null;
function processReqChange() {
if (req.readyState == 4 && req.status == 200 && req.responseXML ) {
var dtable = document.getElementById( 'dataBody' );
var nl = req.responseXML.getElementsByTagName( 'book' );
for( var i = 0; i < nl.length; i++ ) {
var nli = nl.item( i );
var elAuthor = nli.getElementsByTagName( 'author' );
var author = elAuthor.item(0).firstChild.nodeValue;
var elTitle = nli.getElementsByTagName( 'title' );
var title = elTitle.item(0).firstChild.nodeValue;
var elTr = dtable.insertRow( -1 );
var elAuthorTd = elTr.insertCell( -1 );
elAuthorTd.innerHTML = author;
var elTitleTd = elTr.insertCell( -1 );
elTitleTd.innerHTML = title;
} } }
function loadXMLDoc( url ) {
if(window.XMLHttpRequest) {
try { req = new XMLHttpRequest();
} catch(e) { req = false; }
} else if(window.ActiveXObject) {
try { req = new ActiveXObject('Msxml2.XMLHTTP');
} catch(e) {
try { req = new ActiveXObject('Microsoft.XMLHTTP');
} catch(e) { req = false; }
} }
if(req) {
req.onreadystatechange = processReqChange;
req.open('GET', url, true);
req.send('');
}
}
var url = window.location.toString();
url = url.replace( /pat2_xml.html/, 'pat2_xml_data.xml' );
loadXMLDoc( url );
</script>
</head>
<body>
<table cellspacing="0" cellpadding="3" width="100%">
<tbody id="dataBody">
<tr>
<th width="20%">Author</th>
<th width="80%">Title</th>
</tr>
</tbody>
</table>
</body>
</html>
|
リスト 9 は、このページのデータです。
リスト 9. pat2_xml_data.xml
<books>
<book>
<author>Jack Herrington</author>
<title>Code Generation in Action</title>
</book>
<book>
<author>Jack Herrington</author>
<title>Podcasting Hacks</title>
</book>
<book>
<author>Jack Herrington</author>
<title>PHP Hacks</title>
</book>
</books>
|
 | |
実際の環境でこのコードがどのように表示されるかを確認するには、pat2_xml.html のオンライン・バージョンを参照してください。 .
|
|
ブラウザーでこのページをロードすると、図 6 のように表示されます。
図 6.XML の表示ページ
このページと前のパターンのページとの大きな違いは、processReqChange 関数にあります。上記では、responseText の代わりに responseXML が使用されています。responseXML は DOM (Document Object Model) の 1 つで、サーバーからの応答が正しくエンコードされた XML である場合にのみ使用可能になります。
responseXML を使用して XML 文書からの <book> タグのリストを要求し、それぞれのタグから <title> 要素と <author> 要素を取得しています。次に本ごとの表に行を追加し、各行にセルを追加して著者とタイトルのデータを含めています。
これはかなり初歩的な XML データの使用方法です。より高度な JavaScript コードでは、返されたデータに基づいてクライアント側でソートしたり、検索することもできます。
残念ながら XML データを転送すると、ブラウザーが XML 文書全体を解析するのに時間がかかるというマイナス面があります。また、XML 内のデータを検索するための JavaScript コードも複雑になります (リスト 8 を参照)。そこで、代わりの方法として使えるのは、サーバーに JavaScript コードを要求することです。
パターン 3: JavaScript データの読み取り
サーバーに対して JavaScript データを要求するというのは、JSON (JavaScript Object Notation) というしゃれたコード名で知られる手法です。JavaScript データを返す利点は、ブラウザーにとっては JavaScript データ構造のほうがはるかに使いやすく、効率的に解析および作成できるというところにあります。
サーバーからの XML を読み取るリスト 8 のコードを、サーバーからの JavaScript データを読み取るコードに変更すると、新しいコードはリスト 10 のようになります。
リスト 10. pat3_js.html
<html><head><script>
var req = null;
function processReqChange() {
if (req.readyState == 4 && req.status == 200 ) {
var dtable = document.getElementById( 'dataBody' );
var books = eval( req.responseText );
for( var b in books ) {
var elTr = dtable.insertRow( -1 );
var elAuthorTd = elTr.insertCell( -1 );
elAuthorTd.innerHTML = books[b].author;
var elTitleTd = elTr.insertCell( -1 );
elTitleTd.innerHTML = books[b].title;
} } }
... |
HTML コード全体はそのままで、processReqChange 関数がサーバーから返された JavaScript データである eval を読み取るように変更されているだけです。processReqChange 関数は、eval から読み取った JavaScript オブジェクトをデータ・ソースとして使用し、このデータ・ソースが表に追加されます。
リスト 11 は、サーバーからの JavaScript データです。
リスト 11. pat3_js_data.js
[ { author: 'Jack Herrington', title: 'Code Generation in Action' },
{ author: 'Jack Herrington', title: 'Podcasting Hacks' },
{ author: 'Jack Herrington', title: 'PHP Hacks' }
]
|
 | |
実際の環境でこのコードがどのように表示されるかを確認するには、pat3_js.html のオンライン・バージョンを参照してください。 .
|
|
多くの Ajax アプリケーション・エンジニアが XML ではなく、JavaScript コードを使ってデータをエンコードする理由は一目瞭然です。JavaScript コードのほうが、読みやすく管理しやすいだけでなく、ブラウザーにとっても処理が簡単だからです。
この一連のデータ収集と表示では、Ajax の鍵となっているのが現行データの表示であることがわかります。ここで重要なのは、データが現行のものであることです。そこで質問です。サーバーから常に新しいデータを取得するようにするには、どうすればいいでしょう。
パターン 4: ブラウザー・キャッシュの回避
ブラウザーは常に Web トラフィックの最適化を試みるため、同じ URL を 2 度要求すると、ブラウザーはページを再度要求する代わりに、ブラウザー・キャッシュに保管されたページを使用するでしょう。そこで、Ajax アプリケーションに共通のパターンとして、URL 内の要素をランダム化し、ブラウザーがキャッシュに入れられた結果を返さないようにするというパターンがあります。
私が気に入っているのは、現在時刻の数値を URL に追加するという手法です。リスト 12 に、この手法を示します。
リスト 12. pat4_cache.html
<html>
<script>
...
function loadUrl( url ) {
url = url + "?t="+((new Date()).valueOf());
...
}
...
|
上記はリスト 1 のコードに、URL ストリングに JavaScript によるテキスト操作を加えたものです。URL に、時刻の値を持つ新しいパラメーター t を追加しています。サーバーがこの値を認識するかどうかは問題ではありません。これは、ブラウザーが URL に基づくページ・キャッシュを無視することを確実にするための手段でしかないからです。
パターン 5: 複数 HTML セグメントの置換
最後に紹介するパターンは最初のパターンの高度なバージョンで、<div> タグをサーバーからのコンテンツに置き換えるというパターンです。Web アプリケーションでは、ユーザー入力に応答して、複数の表示域を更新しなければならないことが問題になる場合がよくあります。例えば、表示の一部に最新の相場を表示し、別の部分に最新の株価リストを表示する株価情報のアプリケーションがあったとします。
この複数の表示域を更新するために私が使用したのは、両方のセクションのデータが含まれるサーバーからの XML 応答です。次に正規表現を使用して、応答から個別のセクションを取り出しています。リスト 13 に、この手法を示します。
リスト 13. pat5_multi_segment.html
<html>
<script>
...
function loadUrl( url ) {
url = url + "?t="+((new Date()).valueOf());
...
}
...
|
リスト 14 に、サーバーからのデータを示します。
リスト 14. pat5_data.xml
<segments>
<one>Content for segment one</one>
<two>Content for segment <b>two</b></two>
</segments>
|
ブラウザーでこのページをロードすると、図 7 のように表示されます。
図 7. サーバーからのデータで更新された 2 つのセグメントの表示
ページ・コードでは、XML 応答を使用することもできました。サーバーから返される内容は整形式の XML だからです。ただし、XML コードから個別のセグメントを解読するには正規表現を使用したほうが簡単なので、このようなコードになりました。
まとめ
Ajax は非常に強力であると同時に誤解も多く、誤った方法で使用されています。Web アプリケーションで Ajax を使用する出発点としてふさわしいのは、この記事で紹介したパターンです。ただし、ここに記載したコードを使用するだけでなく、Web 2.0 革命に伴って生まれた、優れた Ajax および Web UI ライブラリーもいくつか調べてみることをお勧めします。なかでも代表格は、Prototype.js ライブラリーです。このライブラリーはサーバーとデータをやり取りするための簡単なメソッドから、特定ブラウザーに依存せずに Web ページ・コンテンツを更新するメソッドまで提供しています。これらのライブラリーを使用する利点は、熱心なエンジニアが多種多様なブラウザーとプラットフォームでメソッドを管理およびテストすれば、作業の負担と頭痛の種を大幅に取り除ける可能性があることです。
アプリケーションに動的振る舞いを追加するには、どのように取り掛かるにしても、この記事で紹介したパターンが示す Ajax の調査は不可欠です。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample Ajax pattern files for this article | x-ajaxxml2_ajax_patterns.zip | 7KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
-
Yahoo! UI ライブラリー: Yahoo! の UI ライブラリーを入手してください。これは、Web アプリケーションで Ajax と併せて使用できる Web UI キットです。
-
ATLAS ライブラリー: Microsoft が Ajax ブラウザー互換性レイヤーに採用した ATLAS ライブラリーを試してみてください。このライブラリーは Microsoft® ASP.NET を使用していなくても利用できます。
-
Scriptaculous: Prototype.js ライブラリーを使用した、ブラウザーに依存しないエフェクトおよびウィジェットのライブラリーをダウンロードしてください。
-
Moo.fx: Prototype.js をベースにした軽量なウィジェット・ライブラリーを入手してください。
議論するために
著者について  | |  | Jack D. Herringtonは、20年以上の経験を持つシニア・ソフトウェア・エンジニアです。著者には、「Code Generation in Action」、「Podcasting Hacks」、そして近々刊行予定の「PHP Hacks」の3冊があります。彼は30本以上の技術記事も執筆しています。 |
記事の評価
|