PHP+XML+jQuery實現即時功能
Google的即時功能是一種新的搜索增強功能,隨著您的鍵入顯示結果,它已經獲得了眾多矚目,而且很容易明白這是為什么。獲取結果所需做的全部工作就是鍵入。您無需按Enter鍵來查看結果,然后調整您的搜索并再次按Enter鍵。這都將隨著您的鍵入而發(fā)生。如果您還沒有這樣做,請嘗試一下。令人驚訝的是這么小的變化卻能在可用性上產生如此大的差異。
這類即時功能的主要好處在于易于實現,尤其當您在使用jQuery等客戶端工具時。在本文中,您要遵循構建簡單搜索引擎的流程,而后再為該引擎構建即時搜索用戶界面。這一切都始于獲取搜索數據。
設置數據
對于本文,我決定搜索“辛普森一家”劇集。我創(chuàng)建一個包含所有 “辛普森一家” 劇集、標題、季數、集數、播放日期和每集摘要的 XML 文件(包括在源代碼 下載 中)。您可以在清單 1中看到該XML的一部分。
- 清單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>
它實際上是一個非常大的文件,其大小約為840K。這應該不令人意外,因為 “辛普森一家” 已經播放了漫長的22年。接下來就是編寫一個PHP類,可為您執(zhí)行XML解析和搜索。這個類稱為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文件。它迭代根節(jié)點的所有子節(jié)點并提取它們的季數、標題、播放日期和劇集屬性,以及包含摘要的節(jié)點的文本。然后將所有數據作為一個哈希表附加到劇集數組,該數組是一個成員變量。
然后,find函數搜索劇集列表以便使用與標題和摘要匹配的簡單正則表達式來查找匹配項。任何匹配的劇集都被附加到一個數組,然后返回給調用方。如果數組為空,則沒有發(fā)現匹配項。
現在有了數據,下一步就是開始構建Ajax響應程序,您的即時UI將調用該響應程序來檢索數據。
#p#
創(chuàng)建Ajax響應頁面
第一個版本的UI針對Ajax請求使用HTML響應。此方法是實現即時UI的最簡單的方式。即時UI web頁面采用搜索關鍵詞并使用該關鍵詞對服務器發(fā)出Ajax 請求。然后,服務器格式化組成該響應的HTML塊并將其返回到頁面。在一個簡單的調用中,即時UI web頁面中的代碼將使用更新的 HTML 替換該頁面的一部分。
在本文的后面,我會演示使用來自服務器的XML響應和JSON響應,但是現在,為了簡單起見,我們從HTML版開始。
您首先需要的是HTML響應頁面。此頁面接受來自請求的查詢字符串。然后使用該字符串調用Simpsons類來搜索劇集。接著,將已返回的劇集數組格式化 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類。然后該代碼創(chuàng)建該類的一個新實例并進行find調用。之后它會查看響應是否為空,以及是否返回 “No Results Found”;否則,它遍歷這些結果并組成一個結果表。
為了測試該頁面,只需轉到您的Web瀏覽器并請求該網頁。您可以在圖 1中看到輸出。
此時,您擁有開始構建即時搜索UI所需要的所有東西。
#p#
構建即時搜索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樣式表,以使輸出更美觀。頁面的主體包括搜索關鍵詞的輸入字段和保存此輸出的結果div。
工作的大部分都在頁面底部的 JavaScript 部分完成。它首先調用文檔中的 ready 函數。此調用可確保在頁面準備就緒前都不執(zhí)行內部 JavaScript。內部 JavaScript 使用搜索關鍵詞輸出對象中的 keyup 函數來監(jiān)控搜索關鍵詞字段中的關鍵字鍵入。在文本字段變更時,將對服務器調用 Ajax get 方法。通過使用 html 函數,來自此調用的數據響應將用于填充結果元素。
如果JavaScript代碼看上去像線路噪聲,沒關系。這實際上就是JavaScript的技術現狀,該代碼需要復查線路,所以代碼規(guī)模最好保持得小一些。
雖然您可以在沒有jQuery庫的情況下完成這些工作,但是使用該庫的價值在于,該代碼非常簡潔且所有跨平臺工作都已經為您完成。您不必擔心Internet Explorer與Safari或Firefox;只需編寫一次代碼然后將其用于任何地方。
要測試該庫,請在Web瀏覽器中建立即時搜索UI。在圖 2中,您可以看到類似的東西。
圖2顯示了我輸入少量字符后的界面。在我鍵入關鍵詞 “frink” 之后,您可以在 圖 3 中看到結果。
圖3顯示了在標題或兩集概要中出現 “frink”。可笑的數據!Frink教授(到目前為止節(jié)目中的最佳角色)出現在不止兩集中。但這仍是非常奇妙。本地計算機上的響應時間是非常出色的,即使服務器代碼解析是通過840K的XML。
現在您可能想通過在每一個按鍵之間放置一個延遲并在您真正發(fā)出請求時來控制請求的數量。在清單 5中,更新的代碼可以執(zhí)行此操作。
- 清單 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>
此代碼在用戶按鍵時創(chuàng)建一個計時器。當該計時器在 200 毫秒后停止時,請求發(fā)出。如果另外一次擊鍵在計時器停止之前,則原來的計時器將被取消,同時創(chuàng)建一個新的計時器。其結果就是在用戶停止了鍵入之后,計時器停止 200 毫秒。雖然該界面始終感覺像原來的,但是向服務器發(fā)出請求的數量會大幅減少,尤其是在用戶快速鍵入的時候。
雖然我們可以到此為止,但是實際上有另外兩種方式來進行此即時過程。
#p#
遷移到XML
第一種方式是使用XML作為您從服務器到客戶端的傳輸語法。此處的想法是服務器提供一個通用XML端點,任何流程都可以使用該端點來執(zhí)行查詢,并且您的客戶端足夠智能,能夠讀取XML并以其想要的方式來格式化它。
要變成XML,首先要創(chuàng)建如清單 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();
- ?>
雖然搜索仍然一樣,但是您格式化結果的方式變了?,F在,該代碼創(chuàng)建了XML文檔并將節(jié)點附加到其上,從而保存所有返回的數據。然后在腳本的末尾,它只是將XML DOM保存為字符串。請注意在腳本的頂部,在導出XML而非HTML時,還要將內容類型設置為文本/xml。
如果在您的Web瀏覽器中導航到此頁面,則您會看到類似圖 4中的情況。
不過,一些瀏覽器可能以更結構化的方式顯示已返回的文本。如果您想查看原始的源XML,則您可以選擇View - Source以便看到類似圖 5中的窗口。
如您所見,該腳本創(chuàng)建了一些良好格式化的XML,準備好供新的客戶端代碼使用。
解析XML(而不是直接使用 HTML)的新客戶端代碼位于清單 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> ';
- html += 'Season '+ep.attr('season')+' ';
- html += 'Episode '+ep.attr('episode')+' ';
- html += 'Aired '+ep.attr('aired')+'</td></tr>';
- html += '<tr><td class="summary">'+
- ep.text()+'</td></tr>';
- } );
- html += '</html>';
- $('#results').replaceWith( html );
- } );
- } );
- } );
- </script>
- </body>
- </html>
用于監(jiān)控擊鍵和發(fā)出Ajax請求的客戶端代碼幾乎完全一樣。所不同的是不同的URL獲取XML數據而不是HTML數據。
在數據返回以后,該代碼使用jQuery來尋找所有劇集標簽。然后它可格式化大量XML并使用replaceWith函數來用新表替換舊表。由于使用了jQuery,此代碼比在使用瀏覽器的原生 DOM 功能時更容易使用。
傳輸數據的另一種方式是通過JSON(JavaScript對象符號)。
#p#
遷移到JSON
在Web 2.0的世界中,JSON是一種非常流行的移動數據的方式。它小巧、方便、快捷,便于瀏覽器讀取,因為需要做的所有操作就是評估已返回的JavaScript代碼。創(chuàng)建JSON也很簡單,就如您在清單 8中的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_encode函數來將已返回的數組轉變?yōu)镴SON代碼。如果您好奇的話,這里也存在一個可將JSON轉回為PHP基本類型的json_decode函數。大多數流行語言都具有與這里一樣簡單的JSON機制,可以將基本數據結構轉化為JSON,或者轉換出JSON。
如果您在瀏覽器中查看JSON頁面,則您會看到類似圖 6中的響應頁面。
雖然本頁面可能不會太吸引人們的目光,但是對于瀏覽器中的JavaScript解譯器來說,此頁面看起來非常易于閱讀。用于讀取 JSON 格式化輸出的相應的即時UI web代碼位于清單 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> ';s
- html += 'Season '+ep.season+' ';
- html += 'Episode '+ep.episode+' ';
- 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的開發(fā)人員所做的相比,此實現具有三個主要區(qū)別。首先是縮放。他們每天要處理數十億的搜索,現在通過每一次擊鍵他們可處理數十億單獨的小搜索。雖然存在許多有關這方面的問題和解決方案,但是在這種情況下您至少有一件事情要去做 — 瀏覽器緩存。如果用戶鍵入相同的關鍵詞兩次,由于瀏覽器緩存的緣故,實際上僅進行一個請求,因為第二次發(fā)出請求時瀏覽器返回緩存的數據。
Google 所作的另外一件事情是預取結果。例如,如果您鍵入 “mov”,則暗示您正在尋找 “movies”,進行此搜索并通過用于您缺少的 “ies” 的灰色文本指示這點。
第三個區(qū)別是對于分頁的支持,這相當容易解決。您所要做的就是在頁面的底部為頁面鏈接添加一些 JavaScript,然后在用戶單擊以從第一頁瀏覽到任何隨后頁面時調用此腳本。
結束語
Google的即時UI功能真的很即時。它是革命性的嗎?不是。但是它是對可用性具有深遠影響的一小步。正如您從本文中看到的,通過使用像XML、PHP和jQuery這樣的標準工具,這些行為的初步階段并不難實現。
【編輯推薦】