はじめに
AJAXという言葉を聞いたことはないでしょうか? AJAX(Asynchronous JavaScript and XML)は、Webプログラミングの世界で流行の先端を行く手法であり、ブラウザでページの再読み込みを行わなくてもページ上のデータを動的に更新できることから、人気が高まっています。この記事では、AJAXのしくみについて解説し、その動作を実際に試すためのサンプルコードを紹介します。
この言葉の知名度が高まったのはGoogleのいくつかの最新機能のおかげですが、中でも筆頭に挙げられるのは「Google Suggest」です。これは、検索語をタイプしているときに、人気度に応じて検索語の候補が表示されるという機能です。Google Suggestを使ったことがない人は、このリンク先から試してみてください。
ただし、この機能で使われている技術はAJAXだけではありません。ドロップダウンリスト自体にはDHTMLが使われています。そちらについては当記事では解説しません。
AJAXは新しい技術なのでしょうか。その答えは、「はい」と「いいえ」のどちらも不正解です。「複合性に重きを置いた、既存の技術の新しい応用例」とでも言えば、正解に近いでしょう。具体的には、使用されている技術は、標準のHTMLコントロール、JavaScript、XML HTTPコンポーネント、およびXMLデータ構造です。
手順
次に示すのは、AJAXを使用するときのイベントの流れの概要です。
- Webページが表示されます。
- トリガにより、JavaScriptの関数呼び出しが実行されます(
onKeyUp
、ボタンクリック、setTimeout
、ページロードなど)。 - JavaScriptがXML HTTPオブジェクトのインスタンスを生成します。
- XML HTTPオブジェクトがリモートページを呼び出します。
- リモートページが、XSLTを使用してXML構造を変換し、結果を返します。
- JavaScriptがその結果を受け取り、ページに適用します。
- あら不思議!ページの再読み込みなしで、動的なデータが取得されました。
早速チャレンジ
以上のような手順だということはわかりましたが、サンプルアプリケーションがないと、その具体像を思い浮かべるのは困難です。著者としての責任を果たすべく、以上の手順を説明するためのサンプルを示していくことにします。
ここで行っていることをきちんと理解していただくためには、XML HTTPオブジェクトについての説明が重要だと思います。XML HTTPを使用すると、コードからリモートの場所に接続し、GET要求およびPOST要求を非同期で実行できます。つまり、リモートホストへの接続と要求の送信を行ってから、別のロジックを引き続き実行できます。そして、リモートホストから応答が返ると、リターンイベントを処理するための関数でデータを受け取って、受信内容に応じた判断を行うことができます。リモートホストとの間でやり取りするデータは、XML形式でなくてもかまいません。XMLとは単に、きちんと整形された文字列にすぎません。私がこれまでに経験してきた中には、XML形式以外の文字列を渡すのが目的の処理にはぴったりだというケースも1度ならずありました。XML HTTPオブジェクトは、ブラウザやオペレーティングシステムによっては互換性がない場合もあります。これはクライアント側の関数であるため、その実装は、サーバーではなくクライアントマシンの役目です。
私は、ASP 3.0の頃から、Webページからのリモート呼び出しにこのオブジェクトを使用しています。その威力はすごいものです。クライアントは、データやプロセスに対するアクセスを別個の場所から行うことができ、今いるドメインやページを離れるという手間が不要なのです。
JavaScript
JavaScriptは、AJAXの中核となる部分です。変更の検知、データの要求と受信、ページ上へのデータの配置を処理します。
requestURL
変数は、アクセスするaspxファイルのパスに合わせて修正してください。
使用するJavaScriptのコードは次のとおりです。
<script> var xmlHttp; var requestURL = 'http://localhost/misctest/getusernames.aspx?q='; var is_ie = (navigator.userAgent.indexOf('MSIE') >= 0) ? 1 : 0; var is_ie5 = (navigator.appVersion.indexOf("MSIE 5.5")!=-1) ? 1 : 0; var is_opera = ((navigator.userAgent.indexOf("Opera6")!=-1)|| (navigator.userAgent.indexOf("Opera/6")!=-1)) ? 1 : 0; //netscape, safari, mozilla behave the same??? var is_netscape = (navigator.userAgent.indexOf('Netscape') >= 0) ? 1 : 0; function show_data(strName){ if (strName.length > 0){ //Append the name to search for to the requestURL var url = requestURL + strName; //Create the xmlHttp object to use in the request //stateChangeHandler will fire when the state has changed, //i.e. data is received back // This is non-blocking (asynchronous) xmlHttp = GetXmlHttpObject(stateChangeHandler); //Send the xmlHttp get to the specified url xmlHttp_Get(xmlHttp, url); } else { //Textbox blanked out, clear the results document.getElementById('nameList').innerHTML = ''; } } //stateChangeHandler will fire when the state has changed, // i.e. data is received back // This is non-blocking (asynchronous) function stateChangeHandler() { //readyState of 4 or 'complete' represents that data has been returned if (xmlHttp.readyState == 4 || xmlHttp.readyState == 'complete'){ //Gather the results from the callback var str = xmlHttp.responseText; //Populate the innerHTML of the div with the results document.getElementById('nameList').innerHTML = str; } } // XMLHttp send GET request function xmlHttp_Get(xmlhttp, url) { xmlhttp.open('GET', url, true); xmlhttp.send(null); } function GetXmlHttpObject(handler) { var objXmlHttp = null; //Holds the local xmlHTTP object instance //Depending on the browser, try to create the xmlHttp object if (is_ie){ //The object to create depends on version of IE //If it isn't ie5, then default to the Msxml2.XMLHTTP object var strObjName = (is_ie5) ? 'Microsoft.XMLHTTP' : 'Msxml2.XMLHTTP'; //Attempt to create the object try{ objXmlHttp = new ActiveXObject(strObjName); objXmlHttp.onreadystatechange = handler; } catch(e){ //Object creation errored alert('IE detected, but object could not be created. ' + 'Verify that active scripting and activeX controls are enabled'); return; } } else if (is_opera){ //Opera has some issues with xmlHttp object functionality alert('Opera detected. The page may not behave as expected.'); return; } else{ // Mozilla | Netscape | Safari objXmlHttp = new XMLHttpRequest(); objXmlHttp.onload = handler; objXmlHttp.onerror = handler; } //Return the instantiated object return objXmlHttp; } function UseValue(strVal){ document.frmStuff.txtName.value = strVal; } </script>
クライアントページ(HTML)
JavaScriptを除くと、クライアントページはきわめて単純です。肝心なのは、onKeyUp
イベントを指定したテキストボックスを持つ簡単なフォームだけです。また、ここでは、結果データを表示するためのDIVタグも指定しています。HTMLは次のとおりです。
<html> <head> <title>Ian Suttle's AJAX Sample</title> <style> body, input {font-family: arial; font-size: 12px;} </style> <!-- Insert JavaScript Here --> </head> <body> <form name="frmStuff" id="Form1"> <table border="0" cellpadding="4" cellspacing="0" id="Table2"> <tr> <td width="100">Name:</td> <td><input type="text" name="txtName" id="txtName" autocomplete="off" onkeyup="show_data(this.value);"></td> </tr> <tr> <td width="100" valign="top">Suggestions:</td> <td> <div id="nameList"></div> </td> </tr> </table> </form> </body> </html>
リモートページ
JavaScriptから要求が行われると、「リモートページ」に対するアクセスが行われます。クエリ文字列には、ユーザーが入力したデータを表す変数q
が指定されています。これを受けて、検索条件に合致する有効な要素のみを使用して、XML文字列を組み立てます。ここでは、クライアント側のスクリプトで整形を行うという面倒をなくすために、XMLデータに対してXSL変換を適用しています。XMLの整形をサーバー側で行うという方法は、クライアント側のJavaScriptで行う方法より、はるかに優れています。ブラウザによってはXMLオブジェクトおよびXSLオブジェクトがサポートされていない場合があるという点は、重要なポイントとして頭に入れておいてください。データが常に同じように整形されるよう、必ずサーバー側のロジックを使用しましょう。
「GetUsernames.aspx」というページを作成します。必要な@Page
行以外は、すべてのHTMLを削除します。ページに表示する出力内容は分離コードで作成します。分離コードファイルに次のコードを追加します。
using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.Xml; using System.Xml.Xsl; using System.Xml.XPath; namespace MiscTest { /// <summary> /// Summary description for GetUsernames. /// </summary> public class GetUsernames : System.Web.UI.Page { private void Page_Load(object sender, System.EventArgs e) { GetUsernameList(); } #region Web Form Designer generated code override protected void OnInit(EventArgs e) { // // CODEGEN: This call is required by the ASP.NET Web Form Designer. // InitializeComponent(); base.OnInit(e); } /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.Load += new System.EventHandler(this.Page_Load); } #endregion private void GetUsernameList() { //Get the request query string strQuery = Request["q"].ToString(); //Create the XML-like string to be sent back to the request string strXmlNames = ""; //An arbitrary array of names that will be written to an XML Document. string[] arrStrNames = new string[5]{"Ian Suttle", "John Doe", "Alex Wright", "Albert Einstein", "Sierra Tracy"}; //Loop through the names, creating a psuedo XML element for each foreach(string strName in arrStrNames) { //If the request matches the beginning of a name, // then add it to the string // otherwise it shouldn't be a valid match if (strName.ToLower().Substring(0, strQuery.Length) == strQuery.ToLower()) strXmlNames += "<user><name>" + strName + "</name></user>"; } //Prepend and append the parent element strXmlNames = "<?xml version=\"1.0\" ?><users>" + strXmlNames + "</users>"; //Create an XmlDocument object to store the XML string that we created XmlDocument xDoc = new XmlDocument(); xDoc.LoadXml(strXmlNames); //Create a navigator object to use in the XSL transformation XPathNavigator xPathNav = xDoc.CreateNavigator(); //Create the XSLTransform object XslTransform xslt = new XslTransform(); xslt.Load(Server.MapPath("Names.xslt")); //Do the transformation and send the results out to // the Response object's output stream xslt.Transform(xPathNav, null, Response.OutputStream); } } }
XSLスタイルシート
XSLドキュメントは、XMLデータを所定の表示形式に整形するために使用します。変換を実行するためのコードは次に示すとおりですが、このしくみの詳細については、本記事のテーマから外れるので割愛します。「Names.xslt」というXSLドキュメントを新規作成し、次のコードを貼り付けます。
<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <xsl:for-each select="users/user"> <xsl:value-of select="name" /><br /> </xsl:for-each> </xsl:template> </xsl:stylesheet>
まとめ
前述のとおり、AJAXとは、何年も前から存在する技術の応用例の1つにすぎません。ASP.NETが陰で魔法を使っているわけではないのです。今回紹介した手法には、従来のASPとJavaScriptで実現できないことは何も含まれていません。だからと言って、従来のASPに逆戻りしようという話ではありません。それに、ASP.NET 2.0では、ページからのリモート呼び出しをまったく異なる方法で行うことができます。
AJAXには、気の利いた利用方法がいろいろと考えられます。通知の更新、電子メールのチェック、プロセスの監視などです。開発者の想像力しだいで、その可能性はいくらでも広がっていきます。