Flickr や GMail、Google Suggest、あるいは Google Maps を使った経験がある人は誰も、新しい系統の動的 Web アプリケーションが登場してきていることに気づくでしょう。それらアプリケーションは、プラグインやブラウザに固有の機能に頼ることなく、従来のデスクトップアプリケーションに非常によく似た外観を持ち、非常によく似た動作をします。従来、Web アプリケーションは一群の HTML ページであり、その内容の一部を変更するためには、再度読み込まれる必要がありました。JavaScript や階層式スタイルシート (CSS) などの技術が成熟し、これらを効果的に利用することによってどの主要ブラウザでも機能する非常に動的な Web アプリケーションを作成できるようになったのは、この数年のことです。このドキュメントおよび関連する対処法では、Web アプリケーションをデスクトップアプリケーションのようにリッチで対話性に優れたものにするためにすぐに利用できるいくつかのテクニックについて詳しく説明します。
JavaScript を使用することにより、HTML ページは、読み込まれた元のサーバーの呼び出しと XML ドキュメントのフェッチを非同期に実行できます。JavaScript は、フェッチされたその XML ドキュメントを使用し、HTML ページの DOM (Document Object Model) を更新あるいは変更することができます。最近登場した「AJAX(Asynchronous JavaScript and XML)」 という用語は 、この対話モデルを表します。
AJAX は、新しい技術ではありません。これらのテクニックは、長年の間、Windows プラットフォーム上でインターネットエクスプローラ (IE) 向けの開発を行なっている開発者が利用できるようになっていました。このテクノロジは、ごく最近まで、Web リモーティングあるいはリモートスクリプティングと呼ばれていました。Web 開発者はまた、しばらくの間、プラグインや Java アプレット、隠しフレームを利用して、この対話モデルをエミュレートしていました。最近になって変わったことは、あらゆるプラットフォームにまたがる主要ブラウザで、XMLHttpRequest
オブジェクトに対するサポートを含めることが一般的になったことです。JavaScript の XMLHttpRequest
オブジェクトがもたらした結果は、本当に素晴らしいと言えます。このオブジェクトは、正式な JavaScript 仕様では規定されていませんが、今日あるすべての主要ブラウザによってサポートされています。Firefox や IE、Safari などの現行世代のブラウザの間には、JavaScript および CSS サポートに関して微妙な違いがありますが、管理できない違いではありません。ただし、古いブラウザをサポートする必要がある場合、AJAX では対応できない可能性もあります。
AJAX ベースのクライアントが他と異なるのは、JavaScript として埋め込まれた特定の制御ロジックがクライアントに含まれている点です。ページは、ドキュメントの読み込みやマウスのクリック、フォーカスの変更、さらにはタイマーなどのイベントに基づいて JavaScript と対話します。AJAX 対話では、プレゼンテーションロジックとデータを明確に分離できます。HTML ページは、変更内容を表示する必要があるたびにページ全体を再読み込みするのではなく、必要に応じて少量のデータを取り込むことができます。AJAX がこの対話モデルをサポートするには、別のサーバー側アーキテクチャーが必要です。従来、サーバー側 Web アプリケーションでは、クライアント側のあらゆるイベントに対して HTML ドキュメントを生成することに焦点が当てられていました。クライアント側でイベントが発生すると、サーバーへの呼び出しが行われ、クライアントは応答ごとに HTML ページ全体を再表示し、再描画します。リッチ Web アプリケーションでは、サーバー側コンポーネントから読み出された XML データを使用して、クライアント側イベントに基づいて内容を挿入するためのテンプレートまたはコンテナとして機能する HTML ドキュメントをクライアントがフェッチすることに焦点が当てられています。
AJAX 対話のユースケースのいくつかを次に示します。
これはあらゆるユースケースを網羅しているわけではありませんが、AJAX 対話のおかげて、従来行なっていたよりもずっと多くのことを Web アプリケーションが行えるようになることは明らかです。これらのメリットの多くは注目に値することではありますが、このアプローチにはいくつか欠点もあります。
XMLHttpRequest
オブジェクトの標準化: XMLHttpRequest
は JavaScript 仕様にまだ取り込まれていません。このことは、その動作が、クライアントによって異なることがあることを意味します。AJAX テクノロジのフレームワークやパターンは、開発者が AJAX 対話モデルを使用したアプリケーションの作成経験を積むにつれて登場してくるでしょう。AJAX 対話に関して、1 つですべてに対応するフレームワークに注力するのは時期尚早です。このドキュメントおよび関連する対処法では、サーブレット、JavaServer Pages、JavaServer Faces、JSTL (Java Standard Tag Libraries) などの既存の J2EE (Java 2 Enterprise Edition) テクノロジで AJAX 対話をサポートする方法に焦点を当てています。
AJAX がどのようなものであり、大まかにどのような課題があるのかについて説明しましたから、ここでは、そのまとめとして、AJAX 対応の J2EE アプリケーションを取り上げます。
1 つの例を考えてみましょう。Web アプリケーションに静的 HTML ページが含まれているとします。あるいは JSP テクノロジで生成された HTML ページに、そのページを再表示することなくサーバー側ロジックでフォームデータを妥当性検査する必要がある HTML フォームが含まれているとします。この妥当性検査ロジックは、ValidateServlet
というサーバー側 Web コンポーネント (サーブレット) によって提供されます。下図は、この妥当性検査ロジックを提供する AJAX 対話の詳細を表しています。
上図に見られるように、AJAX 対話は次の項目で構成されています。
XMLHttpRequest
オブジェクトが作成および初期化される。XMLHttpRequest
オブジェクトが呼び出しを行う。ValidationServlet
によって、その要求が処理される。ValidationServlet
から、結果を含む XML ドキュメントが返される。XMLHttpRequest
オブジェクトが callback()
関数を呼び出し、その結果を処理する。イベントの結果として JavaScript 関数が呼び出されます。この場合は、リンクまたはフォームコンポーネントで、関数 validate()
を onkeyup
イベントにマッピングできます。
<input type="text" size="20" id="userid" name="id" onkeyup="validate();">
上記のフォーム要素は、フォームフィールド内のキーが押されるたびに validate()
を呼び出します。
2. XMLHttpRequest
オブジェクトが作成、初期化される。
XMLHttpRequest
オブジェクトが初期化および構成されます。
var req; function validate() { var idField = document.getElementById("idField"); var url = "validate?id=" + escape(idField.value); if (window.XMLHttpRequest) { req = new XMLHttpRequest(); } else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); } req.open("GET", url, true); req.onreadystatechange = callback; req.send(null); }
validate()
関数は、使用する HTTP メソッドを表す GET または POST の url 文字列、ターゲット URL の文字列、そして呼び出しを非同期に行うかどうかを示す boolean 値の 3 つの引数を必要とする、XMLHttpRequest
オブジェクトを開くメソッドを初期化します。対話が非同期に設定 (true
) されている場合は、コールバック関数を指定する必要があります。この対話用のコールバック関数は、req.onreadystatechange =callback;
という文で設定されています。詳細は、「XMLHttpRequest
オブジェクトが callback()
関数を呼び出し、その結果を処理」の項を参照してください。
3. XMLHttpRequest
オブジェクトが呼び出しを行う。
req.send(null);
文に達すると、呼び出しが行われます。HTTP get の場合、この内容は null
でも、空白のままでもかまいません。XMLHttpRequest
オブジェクトでこの関数が呼び出されると、そのオブジェクトの初期化中に設定された URL に対する呼び出しが行われます。この例の場合は、送信されるデータ (id
) が URL パラメータとして含まれています。
要求が idempotent の場合、つまり、重複する 2 つの要求で同じ結果が返される場合は、HTTP GET を使用してください。HTTP GET メソッドを使用すると、エスケープされた URL パラメータを含む URL の長さが、一部ブラウザとサーバー側 Web コンテナによって制限を受けます。サーバー側アプリケーションの状態に作用するデータをサーバーに送信する場合は、HTTP POST メソッドを使用します。HTTP POST では、次の文を使用することによって、XMLHttpRequest
オブジェクトに Content-Type
ヘッダーを設定する必要があります。
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.send("id=" + escape(idTextField.value));
JavaScript からフォームの値を送信する場合は、それらフィールド値のエンコーディングを考慮してください。JavaScript には escape()
関数があり、ローカライズされた内容が正しくエンコーディングされ、かつ特殊文字が正しくエスケープされるようにするには、この関数を使用します。
4. ValidationServlet
によって、その要求が処理される。
URI "validate" にマッピングされたサーブレットは、そのユーザー ID がユーザーデータベースに含まれているかどうかを検査します。
サーブレットは、他の HTTP 要求に対するのと同じように XMLHttpRequest
を処理します。サーバーが要求から id
パラメータを抽出し、そのパラメータが受け取られたかどうかを妥当性検査する例を次に示します。
public class ValidationServlet extends HttpServlet { private ServletContext context; private HashMap users = new HashMap(); public void init(ServletConfig config) throws ServletException { this.context = config.getServletContext(); users.put("greg","account data"); users.put("duke","account data"); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String targetId = request.getParameter("id"); if ((targetId != null) && !users.containsKey(targetId.trim())) { response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("valid "); } else { response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("invalid "); } } }
この例では、単純な HashMap
を使用してユーザーを含めています。この例では、ユーザー ID として "duke" が送信されたと仮定しましょう。
5. ValidationServlet
から XML ドキュメントが返される。
users HashMap
のユーザー ID リストには、ユーザー ID の "duke" が存在します。ValidationServlet は、"invalid" という値を持つ "message" 要素を含む XML ドキュメントを応答に書き込みます。より複雑なユースケースでは、応答の生成に DOM や XSLT などの他の API が必要になることがあります。
response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); response.getWriter().write("invalid ");
ここでは、2 つのことに注意を払う必要があります。それは、Content-Type
が "text/xml" に設定されていること、そして Cache-Control
は "no-cache" に設定する必要があることです。XMLHttpRequest
オブジェクトは、"text/xml" の Content-Type
の要求を処理するだけであり、Cache-Control
を "no-cache" に設定することによって、同じ URL (URL パラメータを含む) に対する重複する要求で異なる応答が返された場合に、ブラウザによって応答がローカルにキャッシュされるのを防いでいます。
6. XMLHttpRequest
オブジェクトが callback()
関数を呼び出し、その結果を処理。
XMLHttpRequest
オブジェクトは、XMLHttpRequest
オブジェクトの readyState
に変更があったときに callback()
関数を呼び出すように構成されています。次の例では、ValidationServlet に対する呼び出しが行われ、readyState
が、XMLHttpRequest
呼び出しが完了したことを意味する "4" であると仮定します。HTTP ステータスコードの "200" は HTTP 対話の成功を意味します。
function callback() { if (req.readyState == 4) { if (req.status == 200) { // メッセージが有効かどうかに基づいて HTML DOM を更新 } } }
ブラウザは、表示するドキュメントのオブジェクト表現 (DOM と呼ばれる) を維持します。HTML ページ内の JavaScript は、この DOM にアクセスすることができ、ページの読み込み後に JavaScript が DOM を変更することを可能にする API が使用できるようになります。
要求が成功すると、JavaScript コードは HTML ページの DOM を変更できます。ValidationServlet から読み出された XML ドキュメントのオブジェクト表現は、req.responseXML
(ここで req
は XMLHttpRequest
オブジェクト) を使用し、JavaScript コードから利用できるようになります。DOM API は、JavaScript がそのドキュメントから内容をナビゲートし、その内容を使用して、HTML ドキュメントの DOM を変更する手段を提供します。ドキュメントの文字列表現は、req.responseText
を呼び出すことによって読み出されます。ここで、ValidateServlet
から返された次の XML ドキュメントを見ることによって、JavaScript で DOM API を使用する方法を探ってみましょう。
<message> valid </message>
これは、単に文字列 "valid" か "invalid" のいずれかをとる message
要素の送信側を含む単純な XML 例です。より高度な例では、複数のメッセージと複数の有効な名前を含めて、ユーザーに提供することができます。
function parseMessage() { var message = req.responseXML.getElementsByTagName("message")[0]; setMessage(message.childNodes[0].nodeValue); }
parseMessages()
関数は、ValidationServlet
から読み出された XML ドキュメントを処理します。この関数は、message
要素の値で setMessage()
を呼び出し、HTML DOM を更新します。
JavaScript は、数多くの API を使用して、HTML DOM 内の任意の要素に対する参照を得ることができます。そのための方法として推奨するのは、document.getElementById("userIdMessage")
(この中の "userIdMessage" は HTML ドキュメントに現れる要素の id 属性) を呼び出す方法です。要素に対する参照があると、JavaScript を使用し、要素の属性を変更したり、要素のスタイルプロパティーを変更したり、あるいは子の要素を追加、削除、変更したりできます。
要素の本体内容を変更する一般的な手段の 1 つに、次の例に見られるように、要素で innerHTML
プロパティーを設定する方法があります。
<script type="text/javascript"> function setMessage(message) { var userMessageElement = document.getElementById("userIdMessage"); userMessageElement.innerHTML = "<font color=\"red\">" + message + " </font>"; } </script> <body> <div id="userIdMessage"></div> </body>
影響を受けた HTML ページ部分は、innerHTML
の設定に従ってただちに再描画されます。innerHTML
プロパティーに、<image>
や <iframe>
などの要素が含まれていると、その要素の指定内容がフェッチされるとともに、描画も行われます。
この方法の最大の欠点は、<font>
などの他のマークアップを含む JavaScript に、<div> 要素本体が文字列としてハードコーディングされることです。プレゼンテーションと文字列としての JavaScript コードを混在させると、ページが読みとりにくく、編集が難しくなります。
HTML DOM を変更するもう 1 つの方法として、次の例に示すように、新しい要素を動的に作成し、ターゲット要素の子としてその要素を付加する方法があります。
<script type="text/javascript"> function setMessage(message) { var userMessageElement = document.getElementById("userIdMessage"); var userIdMessageFont = document.getElementById("userIdMessageFont"); var messageElement = document.createTextNode(message); if (userMessageElement.childNodes[0]) { // 要素を更新 userIdMessageFont.replaceChild(messageElement, userIdMessageFont.childNodes[0]); } else { // 新しい要素を作成 var fontElement = document.createTextNode("font"); fontElement.setAtribute("id", "userIdMessageFont"); fontElement.setAtribute("color", "red"); userMessageElement.appendChild(fontElement); fontElement.appendChild(messageElement); } } </script> <body> <div id="userIdMessage"></div> </body>
上記のコード例は、JavaScript DOM API を使用して、プログラム上で要素を作成あるいは要素を変更する方法を示しています。ブラウザによって JavaScript DOM API は異なる可能性があり、アプリケーションの開発では、注意が必要です。
すでに紹介したように、AJAX 対話で解決可能と思われる課題は数多くあります。J2EE テクノロジは、HTTP 処理やデータベース、Web サービス、XML 処理、ビジネスオブジェクトを結び付ける API を使用して、AJAX アプリケーションを開発、配備するための格好の土台を提供します。この対話モデルに対する理解を深めることで、今日のアプリケーションの対話性が増し、エンドユーザー体験を豊かにすることができます。
このドキュメントでは、一般的なユースケースと関係するテクノロジを説明し、AJAX 対話の解剖を行なってみました。さらに具体的な例については、さまざまな対処法の項目を参照してください。
Apple Developer に、XMLHttpRequest
オブジェクトに関する有用な資料があります。
W3C 定義の JavaScript DOM バインド。
ブラウザにおける JavaScript および CSS サポートに関する有用な資料 (http://www.quirksmode.org)。
Jesse James Garrett による AJAX の定義。