Google的即时功能是一种新的搜索增强功能,随着您的键入显示结果,它已经获得了众多瞩目,而且很容易明白这是为什么。获取结果所需做的全部工作就是键入。您无需按Enter键来查看结果,然后调整您的搜索并再次按Enter键。这都将随着您的键入而发生。如果您还没有这样做,请尝试一下。令人惊讶的是这么小的变化却能在可用性上产生如此大的差异。
这类即时功能的主要好处在于易于实现,尤其当您在使用jQuery等客户端工具时。在本文中,您要遵循构建简单搜索引擎的流程,而后再为该引擎构建即时搜索用户界面。这一切都始于获取搜索数据。
设置数据
对于本文,我决定搜索“辛普森一家”剧集。我创建一个包含所有 “辛普森一家” 剧集、标题、季数、集数、播放日期和每集摘要的 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类,可为您执行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文件。它迭代根节点的所有子节点并提取它们的季数、标题、播放日期和剧集属性,以及包含摘要的节点的文本。然后将所有数据作为一个哈希表附加到剧集数组,该数组是一个成员变量。
然后,find函数搜索剧集列表以便使用与标题和摘要匹配的简单正则表达式来查找匹配项。任何匹配的剧集都被附加到一个数组,然后返回给调用方。如果数组为空,则没有发现匹配项。
现在有了数据,下一步就是开始构建Ajax响应程序,您的即时UI将调用该响应程序来检索数据。
#p#
创建Ajax响应页面
第一个版本的UI针对Ajax请求使用HTML响应。此方法是实现即时UI的最简单的方式。即时UI web页面采用搜索关键词并使用该关键词对服务器发出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类。然后该代码创建该类的一个新实例并进行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 函数。此调用可确保在页面准备就绪前都不执行内部 JavaScript。内部 JavaScript 使用搜索关键词输出对象中的 keyup 函数来监控搜索关键词字段中的关键字键入。在文本字段变更时,将对服务器调用 Ajax get 方法。通过使用 html 函数,来自此调用的数据响应将用于填充结果元素。
如果JavaScript代码看上去像线路噪声,没关系。这实际上就是JavaScript的技术现状,该代码需要复查线路,所以代码规模最好保持得小一些。
虽然您可以在没有jQuery库的情况下完成这些工作,但是使用该库的价值在于,该代码非常简洁且所有跨平台工作都已经为您完成。您不必担心Internet Explorer与Safari或Firefox;只需编写一次代码然后将其用于任何地方。
要测试该库,请在Web浏览器中建立即时搜索UI。在图 2中,您可以看到类似的东西。
图2显示了我输入少量字符后的界面。在我键入关键词 “frink” 之后,您可以在 图 3 中看到结果。
图3显示了在标题或两集概要中出现 “frink”。可笑的数据!Frink教授(到目前为止节目中的最佳角色)出现在不止两集中。但这仍是非常奇妙。本地计算机上的响应时间是非常出色的,即使服务器代码解析是通过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 毫秒。虽然该界面始终感觉像原来的,但是向服务器发出请求的数量会大幅减少,尤其是在用户快速键入的时候。
虽然我们可以到此为止,但是实际上有另外两种方式来进行此即时过程。
#p#
迁移到XML
第一种方式是使用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 DOM保存为字符串。请注意在脚本的顶部,在导出XML而非HTML时,还要将内容类型设置为文本/xml。
如果在您的Web浏览器中导航到此页面,则您会看到类似图 4中的情况。
不过,一些浏览器可能以更结构化的方式显示已返回的文本。如果您想查看原始的源XML,则您可以选择View - Source以便看到类似图 5中的窗口。
如您所见,该脚本创建了一些良好格式化的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>
用于监控击键和发出Ajax请求的客户端代码几乎完全一样。所不同的是不同的URL获取XML数据而不是HTML数据。
在数据返回以后,该代码使用jQuery来寻找所有剧集标签。然后它可格式化大量XML并使用replaceWith函数来用新表替换旧表。由于使用了jQuery,此代码比在使用浏览器的原生 DOM 功能时更容易使用。
传输数据的另一种方式是通过JSON(JavaScript对象符号)。
#p#
迁移到JSON
在Web 2.0的世界中,JSON是一种非常流行的移动数据的方式。它小巧、方便、快捷,便于浏览器读取,因为需要做的所有操作就是评估已返回的JavaScript代码。创建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函数来将已返回的数组转变为JSON代码。如果您好奇的话,这里也存在一个可将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的开发人员所做的相比,此实现具有三个主要区别。首先是缩放。他们每天要处理数十亿的搜索,现在通过每一次击键他们可处理数十亿单独的小搜索。虽然存在许多有关这方面的问题和解决方案,但是在这种情况下您至少有一件事情要去做 — 浏览器缓存。如果用户键入相同的关键词两次,由于浏览器缓存的缘故,实际上仅进行一个请求,因为第二次发出请求时浏览器返回缓存的数据。
Google 所作的另外一件事情是预取结果。例如,如果您键入 “mov”,则暗示您正在寻找 “movies”,进行此搜索并通过用于您缺少的 “ies” 的灰色文本指示这点。
第三个区别是对于分页的支持,这相当容易解决。您所要做的就是在页面的底部为页面链接添加一些 JavaScript,然后在用户单击以从第一页浏览到任何随后页面时调用此脚本。
结束语
Google的即时UI功能真的很即时。它是革命性的吗?不是。但是它是对可用性具有深远影响的一小步。正如您从本文中看到的,通过使用像XML、PHP和jQuery这样的标准工具,这些行为的初步阶段并不难实现。
【编辑推荐】