PHP、XML、jQuery を使ってインスタント機能を実現する

Ajax を使って Web アプリケーションにインスタント機能を組み込む方法を学ぶ

PHP、XML、jQuery を組み合わせ、Web サイトに「インスタント」スタイルの機能を組み込みましょう。この記事で紹介するコードを皆さんが好きなように選び、使ってみてください。

Jack D. Herrington, Senior Software Engineer, Fortify Software, Inc.

Photo of Jack HerringtonJack Herrington はサンフランシスコ湾岸地域に住む技術者、著作者、講演者です。彼の最新の活動や記事を http://jackherrington.com で知ることができます。



2010年 11月 09日

インスタント入門

よく使われる頭文字語

  • Ajax: Asynchronous JavaScript + XML
  • CSS: Cascading Stylesheets
  • DOM: Document Object Model
  • HTML: HyperText Markup Language
  • JSON: JavaScript Object Notation
  • UI: User Interface
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language

Google が検索機能を強化するために新たに追加したインスタント機能は、ユーザーが検索語を入力している最中に結果を表示することができます。この機能は大きな反響を呼んでおり、その反響の理由は容易に理解することができます。単に検索語を入力していくだけで検索結果の表示が始まるのです。Enter キーを押さなくても結果が表示され、検索語を少し変更して再度 Enter を押せば、変更された検索の結果が表示されます。この機能では、ユーザーが入力するのに従って検索結果が表示されていきます。まだ試していない人は、試してみてください。これほど小さな変更によって、どれほどユーザビリティーに大きな差が生じるか、驚くほどです。

このタイプのインスタント機能の素晴らしい点は、実装が容易であることです。jQuery (「参考文献」) のような優れたクライアント・サイド・ツールを使用すると、なおさら実装が容易になります。この記事では、単純な検索エンジンを作成する手順を説明し、続いてその検索エンジンのためのインスタント検索ユーザー・インターフェース (UI) を作成する方法について説明します。

最初はまず、検索の対象となるデータを入手します。


データをセットアップする

この記事では、「The Simpsons (邦題: ザ・シンプソンズ)」というアニメーション番組の放映回 (以下、エピソードとします) を検索することにしました。1 つの XML ファイル (「ダウンロード」のソースに含まれています) の中に、The Simpsons の全エピソードの情報 (タイトル、シーズン、エピソード番号、放映日、ストーリー) をまとめました。この XML ファイルの一部を示したものがリスト 1 です。

リスト 1. XML データ・ソース
<?xml version="1.0" encoding="UTF-8"?>
<episodes>
  <episode title='Simpsons Roasting on an Open Fire' episode='1' season='1' 
  aired='17 December 1989'>
     Christmas seems doomed for the Simpson family when Homer receives no
    Christmas Bonus. Homer becomes a mall Santa Claus, hoping to make money and
    bring Marge, Bart, Lisa, and baby Maggie, a happy holiday.
  </episode>
   ...
</episodes>

この XML ファイルは実は非常に大きく、約 840K あります。これは驚くには当たりません。The Simpsons は 22 年間もの長期にわたって放送されているからです。

次に、XML の構文解析と検索を行う PHP クラスを作成します。このクラスを Simpsons と呼ぶことにします (リスト 2)。

リスト 2. Simpsons 検索クラス
<?php
class Simpsons {
 private $episodes = array();
 public function __construct() {
   $xmlDoc = new DOMDocument();
   $xmlDoc->load("simpsons.xml");
 foreach ($xmlDoc->documentElement->childNodes as $episode)
   {
     if ( $episode->nodeType == 1 ) {
      $this->episodes []= array( 
      'episode' => $episode->getAttribute( 'episode' ),
      'season' => $episode->getAttribute( 'season' ),
      'title' => $episode->getAttribute( 'title' ),
      'aired' => $episode->getAttribute( 'aired' ),
      'summary' => $episode->nodeValue );
     }
   }
 }
 public function find( $q ) {
   $found = array();
   $re = "/".$q."/i";
   foreach( $this->episodes as $episode ) {
     if ( preg_match( $re, $episode['summary'] ) || 
        preg_match( $re, $episode['title'] ) ) {
     $found []= $episode;
   }
   }
   return $found;
 }
}
?>

このクラスのコンストラクターは、PHP に標準で用意されている XML DOM ライブラリーを使ってエピソード情報の XML ファイルを読み取ります。コンストラクターはルート・ノードのすべての子に対して繰り返し処理を行い、それらの子のシーズン (season) 属性、タイトル (title) 属性、放映日 (aired) 属性、エピソード (episode) 属性、そしてストーリー (summary) を含むノードのテキストを抽出します。そしてこれらのデータをすべて、メンバー変数である episodes 配列にハッシュ・テーブルとして追加します。

次に find 関数はエピソード・リストを検索します。find 関数は単純な正規表現を使ってタイトル (title) とストーリー (summary) に対して突き合わせを行い、一致するものを見つけます。一致するエピソードはすべて配列に追加され、この配列が呼び出し側に返されます。配列が空の場合には、一致するものが見つからなかったことになります。

このデータが用意できたら次のステップに進み、Ajax による応答プログラムを作成します。インスタント UI は、この応答プログラムを呼び出すことでデータを取得します。


Ajax による応答ページを作成する

この UI の最初のバージョンでは、Ajax リクエストに対するレスポンスとして HTML を使います。インスタント UI を実装する方法としては、HTML レスポンスによる方法が最も簡単です。インスタント UI を持つ Web ページは、検索語を取得し、その検索語を使ってサーバーに Ajax リクエストを行います。すると、サーバーはレスポンスを構成する HTML ブロックを作成し、その HTML ブロックを Web ページに返送します。インスタント UI を持つ Web ページのコードは、更新された HTML を使って Web ページの一部を置き換えます。この更新動作は 1 回の簡単な呼び出しで実現することができます。

この記事で後ほど、サーバーからのレスポンスとして XML と JSON を使う方法を説明します。ただしここでは簡単にするために、差し当たっては HTML バージョンで始めることにします。

まず、HTML のレスポンス・ページが必要です。この HTML ページはリクエストからクエリー・ストリングを取得します。次にそのクエリー・ストリングを使って Simpsons クラスを呼び出し、エピソードを検索します。そして返された episode 配列を HTML フォーマットにします。そのためのコードがリスト 3 です。

リスト 3. HTML による Ajax レスポンス・ページ
<?php
include 'Simpsons.php';

$s = new Simpsons();
$episodes = $s->find( $_REQUEST['q'] );
if ( count( $episodes ) == 0 ) {
?>
No results found
<?php	
} else {
?>
<table>
<?php foreach( $episodes as $e ) { ?>
<tr><td class="episode"><b><?php echo( $e['title'] ) 
?></b> - 
 Season <?php echo( $e['season'] ) ?> 
 Episode <?php echo( $e['episode'] ) ?> - 
 Aired on <?php echo( $e['aired'] ) ?></td></tr>
<tr><td class="summary"><?php echo( $e['summary'] ) 
?></td></tr>
<?php } ?>
</table>
<?php
}
?>

リスト 3 の先頭では Simpsons クラスをインクルードしており、それに続いて Simpsons クラスの新しいインスタンスを作成し、find を呼び出しています。次に、レスポンスが空かどうかを確認し、空の場合には「No Results Found (見つかりませんでした)」を返し、空ではない場合には返された結果に対して繰り返し処理を行い、結果の表を作成しています。

このページをテストするためには、単純にブラウザーからこのページをリクエストします。その結果の出力を示したものが図 1 です。

図 1. HTML による Ajax レスポンス・ページ
HTML による Ajax レスポンス・ページのスクリーン・キャプチャー

この時点で必要なものはすべて揃ったので、インスタント検索 UI を作り始めることができます。


インスタント検索 UI を作成する

JavaScript のライブラリーである jQuery を使用すると、インスタント検索 UI を非常に容易に作成することができます。その意味はリスト 4 を見ると理解できるはずです。

リスト 4. HTML レスポンスを使用するインスタント検索ページ
<html><head>
<script src="jquery-1.4.2.min.js"></script>
<link rel="stylesheet" href="styles.css" type="text/css" />
<title>Instant Search - HTML Based</title>
</head>
<body>
Simpsons Search: <input type="text" id="term" />
<div id="results">
</div>
<script>
$(document).ready(function() {
$('#term').keyup(function() {
  $.get('search_html.php?q='+escape($('#term').val()), function(data) {
    $('#results').html(data);
  } );
} );
} );
</script>
</body>
</html>

リスト 4 では、ページの先頭に jQuery ライブラリーと CSS スタイルシートを含めることで出力の見栄えを良くしています。このページの body には、検索語を入力するためのフィールドと、結果を保持するための div が含まれています。

処理の大半は、このページの最後にある JavaScript セクションで行われます。このセクションでは、最初に document の ready 関数を呼び出しています。ready 関数を呼び出しているため、内部にある JavaScript はページの準備が完了するまで実行されません。内部にある JavaScript は term という検索語入力オブジェクトの keyup 関数を使用し、検索語フィールドでキーが押されたかどうかをモニタリングします。テキスト・フィールドが変更されると、Ajax の get メソッドを呼び出してサーバーにアクセスします。その呼び出しに対するレスポンス・データは、html 関数を使って results 要素に追加されます。

JavaScript コードが呪文のように見えるかもしれませんが、それでも構いません。実はこのコードは最先端の JavaScript なのです。このようにすることでコード・サイズは最も小さく保たれ、送受信データの量を最小限にとどめることができます。

これらの処理はすべて jQuery ライブラリーを使用しなくても実現できますが、jQuery ライブラリーを使用することでコードが簡潔になり、クロスプラットフォームの処理をすべてライブラリーが行ってくれます。Internet Explorer® と Safari あるいは Firefox の違いを気にする必要はなく、いったんコードを作成すれば、そのコードはどこでも動作します。

jQuery ライブラリーをテストするためには、Web ブラウザーでインスタント検索 UI を起動します。すると図 2 のようなものが表示されるはずです。

図 2 検索語として何文字か入力した状態
検索語として何文字か入力した状態のスクリーン・キャプチャー

図 2 は何文字かを入力した後のインターフェースを示しています。「frink」という用語の入力を完了すると、表示は図 3 のようになります。

図 3. 用語の入力を完了した後の結果
用語の入力を完了した後の結果のスクリーン・キャプチャー

図 3 から、2 つのエピソードのタイトルまたはストーリーの中に「frink」が登場していることがわかります。しかし、そんなはずはありません。Frink 教授 (このアニメの中で、他を圧倒する最高のキャラクター) が登場するエピソードは、2 つどころではありません。それでも、これはなかなか素晴らしい結果です。と言うのも、サーバー・コードが 840K の XML を構文解析したにもかかわらず、私のローカル・マシンでの応答時間は非常に短いものでした。

今度は、キーが押されてから実際にリクエストを行うまでの各間隔に遅延を挿入し、リクエストの数を絞ります。そのように更新したコードがリスト 5 です。

リスト 5. 遅延のある HTML レスポンスを使用するインスタント検索ページ
<html><head>
<link rel="stylesheet" href="styles.css" type="text/css">
<script src="jquery-1.4.2.min.js"></script>
<title>Instant Search - HTML Based With Delay</title>
</head>
<body>
Simpsons Search: <input type="text" id="term" />
<div id="results">
</div>
<script>
delayTimer = null;

function getResults() {
  $.get('search_html.php?q='+escape($('#term').val()), function(data) {
    $('#results').html(data);
  } );
  delayTimer = null;
}

$(document).ready(function() {
$('#term').keyup(function() {
  if ( delayTimer )
     window.clearTimeout( delayTimer );
  delayTimer = window.setTimeout( getResults, 200 );
} );
} );
</script>
</body>
</html>

このコードでは、ユーザーがキーを押すとタイマーが起動されます。そのタイマーが 200 ミリ秒後にタイム・アウトすると、リクエストが行われます。タイマーがタイム・アウトする前に再度キーが押されると、最初のタイマーはキャンセルされ、新たにタイマーが起動されます。その結果、ユーザーが最後に入力を行ってから 200 ミリ秒経過した後にタイマーがタイム・アウトします。このインターフェースでも最初のインターフェースと同じくらい応答性が高く感じられますが、サーバーに対するリクエスト回数は (ユーザーが短い間隔で連続して入力する場合は特に) 大幅に減少します。

ここで終えることもできますが、実はこの瞬時に行われるプロセスを実現する方法が、他にもまだ 2 つあるのです。


XML に移行する

第 1 の方法は、サーバーからクライアントへのトランスポートの構文として XML を使う方法です。この場合の考え方としては、サーバーは任意のプロセスからクエリーを実行するために使用できる汎用の XML エンドポイントであり、クライアントは XML を読み取って自在に整形できるだけのインテリジェントさを備えている、ということです。

XML に変更するためには、まず新しいサーバー・ページを作成します (リスト 6)。

リスト 6. XML による Ajax ページ
<?php
include 'Simpsons.php';

header( 'Content-type: text/xml' );

$s = new Simpsons();
$doc = new DOMDocument();
$root = $doc->createElement( 'episodes' );
$doc->appendChild( $root );
foreach( $s->find( $_REQUEST['q'] ) as $episode ) {
   $el = $doc->createElement( 'episode' );
   $el->setAttribute( 'title', $episode['title'] );
   $el->setAttribute( 'episode', $episode['episode'] );
   $el->setAttribute( 'season', $episode['season'] );
   $el->setAttribute( 'aired', $episode['aired'] );

   $tn = $doc->createTextNode( $episode['summary'] );
   $el->appendChild( $tn );

   $root->appendChild( $el );
}
print $doc->saveXML();
?>

検索はまったく同じままですが、検索結果のフォーマットが変わります。このコードでは、XML 文書を作成し、その XML 文書に対し、返されるすべてのデータを保持するノードを追加します。そしてスクリプトの最後で、単純に XML の DOM をストリングとして保存しています。また、このスクリプトの先頭でコンテンツ・タイプも text/xml に設定していることに注意してください。これはエクスポートのフォーマットを HTML ではなく XML に変更したためです。

Web ブラウザーでこのページにナビゲートすると、図 4 のようなものが表示されます。

図 4. XML によるレスポンス・ページ
XML によるレスポンス・ページのスクリーン・キャプチャー

ただし一部のブラウザーでは、返されるテキストが少し構造化されて表示されるかもしれません。オリジナルのソースの XML を表示したい場合には、「View (表示)」 - 「Source (ソース)」の順に選択します。すると図 5 のような内容が表示されます。

図 5. XML によるレスポンス・ページのソース
XML によるレスポンス・ページのソースのスクリーン・キャプチャー

これを見るとわかるように、上記のスクリプトによって適切なフォーマットの XML が作成され、クライアント・サイドで新しいコードとして利用できる状態になっていることがわかります。

この新しいクライアント・サイド・コードは HTML を使う代わりに XML を直接構文解析します。このコードを示したものがリスト 7 です。

リスト 7. XML を使ったインスタント検索ページ
<html><head>
<script src="jquery-1.4.2.min.js"></script>
<link rel="stylesheet" href="styles.css" type="text/css" />
<title>Instant Search - XML Based</title>
</head>
<body>
Simpsons Search: <input type="text" id="term" />
<table id="results">
</table>
<script>
$(document).ready( function() {
$('#term').keyup( function() {
 $.get('search_xml.php?q='+escape($('#term').val()), function(data) {
html = '<table id="results">';
$(data).find('episode').each( function() {
     var ep = $(this);
         html += '<tr><td class="episode"><b>'+
         ep.attr('title')+'</b>&nbsp;';
         html += 'Season '+ep.attr('season')+'&nbsp;';
         html += 'Episode '+ep.attr('episode')+'&nbsp;';
         html += 'Aired '+ep.attr('aired')+'</td></tr>';
         html += '<tr><td class="summary">'+
         ep.text()+'</td></tr>';
   } );
   html += '</html>';
   $('#results').replaceWith( html );
 } );
} );
} );
</script>
</body>
</html>

クライアント・コードのうち、キー入力をモニタリングする部分と Ajax リクエストを行う部分はほとんど同じです。異なる部分は、HTML データではなく XML データを取得するために、異なる URL が使われていることだけです。

データが返されると、このコードは jQuery を使ってすべての episode タグを検索します。続いて、XML の大きな部分をフォーマット設定し、replaceWith 関数を使って古い表を新しい表で置き換えます。このコードは jQuery を使っているため、ブラウザー・ネイティブの DOM 関数を使う場合よりも驚くほど使いやすくなっています。

データを送信するための、もう 1 つの方法が、JSON (JavaScript Object Notation) を使う方法です。


JSON に移行する

JSON は Web 2.0 の世界でデータを送受信するために非常によく使われています。JSON は簡潔で容易であり、ブラウザーで読み取る場合も高速です。JSON の場合、ブラウザーは返された JavaScript コードを評価しさえすればよいからです。また、リスト 8 に示す JSON バージョンの Ajax 検索ページを見るとわかるように、JSON の作成も非常に容易です。

リスト 8. JSON による Ajax ページ
<?php
include 'Simpsons.php';

header( 'Content-type: application/json' );

$s = new Simpsons();
print json_encode( $s->find( $_REQUEST['q'] ) );
?>

JSON を作成するには、json_encode 関数を使用して、返された配列を JSON コードに変換するだけでよいのです。興味のある人のために言えば、JSON を PHP の基本型に戻すための json_decode 関数もあります。よく使われるほとんどの言語には、これらの関数と同じように使いやすい JSON メカニズムがあり、基本的なデータ構造と JSON との間での変換をすることができます。

この JSON によるレスポンス・ページをブラウザーで表示すると、図 6 のようになります。

図 6. JSON によるレスポンス・ページ
JSON によるレスポンス・ページのスクリーン・キャプチャー

このページは、人間にはあまりにも読みにくくなっているかもしれませんが、ブラウザーの JavaScript インタープリターにとっては至って容易に読み取れるようになっています。

JSON によるレスポンスに対応し、JSON フォーマットの出力を読み取るためのインスタント検索 UI ページのコードをリスト 9 に示します。

リスト 9. JSON を使ったインスタント検索 UI
<html><head>
<script src="jquery-1.4.2.min.js"></script>
<link rel="stylesheet" href="styles.css" type="text/css" />
<title>Instant Search - JSON Based</title>
</head>
<body>
Simpsons Search: <input type="text" id="term" />
<table id="results">
</table>
<script>
$(document).ready( function() {
$('#term').keyup( function() {
 $.get('search_json.php?q='+escape($('#term').val()), function(data) {
   html = '<table id="results">';
   $.each( data, function( ind, ep ) {
         html += '<tr><td class="episode"><b>'+ep.title+'</b>&nbsp;';s
         html += 'Season '+ep.season+'&nbsp;';
         html += 'Episode '+ep.episode+'&nbsp;';
         html += 'Aired '+ep.aired+'</td></tr>';
         html += '<tr><td class="summary">'+ep.summary+'</td></tr>';
   } );
   html += '</html>';
   $('#results').replaceWith( html );
 } );
} );
} );
</script>
</body>
</html>

このコードは XML のコードと非常によく似ています。異なる点は、返された配列に対して jQuery の each 関数を使用することができ、データの中にある重要なキー (つまり、タイトル、エピソード、ストーリー等々) すべてに対し、ドット表記を使ってアクセスしている点です。

これで、インスタント検索機能の基本的な実装が完成しました。この実装を出発点として、皆さん自身の作業を開始することができます。


ちょっとした追加事項

この実装には、Google の開発者達が行ったこととの大きな違いが 3 つあります。第 1 は規模の違いです。Google は既に毎日何十億件という検索を処理していましたが、今や Google はキーが押されるごとに何十億件という個々の小規模な検索を行っています。そうした検索には、問題がたくさんあり、それに対する解決策もたくさんありますが、この記事の実装の場合には、少なくとも 1 つの問題があります。それは、ブラウザーのキャッシュが利用されることです。ユーザーが同じ検索語を 2 度入力した場合、ブラウザーのキャッシュが利用されるため、実際のリクエストは 1 度しか行われません。同じ検索語が 2 度目に要求された場合には、ブラウザーはキャッシュされたデータを返すからです。

第 2 の違いは、Google が結果のプリフェッチを行っていることです。例えばユーザーが「mov」と入力すると、Google はユーザーが「movies」を検索していると判断して検索を行い、「ies」が足りないことを灰色のテキストを使ってユーザーに知らせます。

第 3 の違いは、ページングのサポートですが、この違いは容易に解決することができます。必要なことは、ページをリンクするための簡単な JavaScript をページの最後に追加し、ユーザーが最初のページから任意のページにナビゲートしようとしてクリックした時に、そのスクリプトが呼び出されるようにすることだけです。


まとめ

Google のインスタント UI 機能は、まさに瞬時に実行されます。この機能は革命的なのでしょうか。決してそんなことはありません。しかしユーザビリティーという点では、小さなステップでありながら、深い意味を持っています。この記事で説明したように、XML、PHP、jQuery といった標準的なツールを使用すれば、インスタント UI の基本的な動作の実装は難しくはありません。

この記事で紹介したコードを皆さん自身のプロジェクトに活用してみてください。そして実際に活用した場合には、私にお知らせください。この記事のコードを皆さんがどのように活用したか、私もぜひ見たいと思っています。


ダウンロード

内容ファイル名サイズ
Source code for articlesrc.zip82KB

参考文献

学ぶために

製品や技術を入手するために

  • jQuery 自動補完プラグインについて調べ、この記事で説明したインスタント検索パターンと似た自動補完パターンを試してみてください。
  • jQuery 以外の JavaScript フレームワークとして、MooToolsPrototype.js も検討してみてください。
  • IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source, Web development
ArticleID=594606
ArticleTitle=PHP、XML、jQuery を使ってインスタント機能を実現する
publish-date=11092010