【IT168 技术】
即时功能入门
Google 的即时功能是一种新的搜索增强功能,随着您的键入显示结果,它已经获得了众多瞩目,而且很容易明白这是为什么。获取结果所需做的全部工作就是键入。您无需按 Enter 键来查看结果,然后调整您的搜索并再次按 Enter 键。这都将随着您的键入而发生。如果您还没有这样做,请尝试一下。令人惊讶的是这么小的变化却能在可用性上产生如此大的差异。
这类即时功能的主要好处在于易于实现,尤其当您在使用 jQuery 等客户端工具时(参见 参考资料)。在本文中,您要遵循构建简单搜索引擎的流程,而后再为该引擎构建即时搜索用户界面。
这一切都始于获取搜索数据。
设置数据
对于本文,我决定搜索 “辛普森一家” 剧集。我创建一个包含所有 “辛普森一家” 剧集、标题、季数、集数、播放日期和每集摘要的 XML 文件(包括在源代码 下载 中)。您可以在 清单 1 中看到该 XML 的一部分。
清单 1. XML 数据源
<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 搜索类
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 将调用该响应程序来检索数据。
创建 Ajax 响应页面
第一个版本的 UI 针对 Ajax 请求使用 HTML 响应。此方法是实现即时 UI 的最简单的方式。即时 UI web 页面采用搜索关键词并使用该关键词对服务器发出 Ajax 请求。然后,服务器格式化组成该响应的 HTML 块并将其返回到页面。在一个简单的调用中,即时 UI web 页面中的代码将使用更新的 HTML 替换该页面的一部分。
在本文的后面,我会演示使用来自服务器的 XML 响应和 JSON 响应,但是现在,为了简单起见,我们从 HTML 版开始。
您首先需要的是 HTML 响应页面。此页面接受来自请求的查询字符串。然后使用该字符串调用 Simpsons 类来搜索剧集。接着,将已返回的剧集数组格式化 HTML。此代码位于 清单 3 中。
清单 3. HTML Ajax 响应页面
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 中看到输出。
▲图1. HTML Ajax 响应页面
此时,您拥有开始构建即时搜索 UI 所需要的所有东西。
构建即时搜索 UI
使用 JavaScript jQuery 库可以轻松构建即时搜索 UI。查看 清单 4,您就可以明白我的意思。
清单 4. 使用 HTML 响应的即时页面
<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 在搜索关键词中键入的少量字母
图 2 显示了我输入少量字符后的界面。在我键入关键词 “frink” 之后,您可以在 图 3 中看到结果。
▲图3 键入关键词之后
图 3 显示了在标题或两集概要中出现 “frink”。可笑的数据!Frink 教授(到目前为止节目中的非常好的角色)出现在不止两集中。但这仍是非常奇妙。本地计算机上的响应时间是非常出色的,即使服务器代码解析是通过 840K 的 XML。
现在您可能想通过在每一个按键之间放置一个延迟并在您真正发出请求时来控制请求的数量。在 清单 5 中,更新的代码可以执行此操作。
清单 5. 使用带有延迟的 HTML 响应的即时页面
<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 毫秒。虽然该界面始终感觉像原来的,但是向服务器发出请求的数量会大幅减少,尤其是在用户快速键入的时候。
虽然我们可以到此为止,但是实际上有另外两种方式来进行此即时过程。
迁移到 XML
第一种方式是使用 XML 作为您从服务器到客户端的传输语法。此处的想法是服务器提供一个通用 XML 端点,任何流程都可以使用该端点来执行查询,并且您的客户端足够智能,能够读取 XML 并以其想要的方式来格式化它。
要变成 XML,首先要创建如 清单 6 中所示的新服务器页面。
清单 6. XML Ajax 页面
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 中的情况。
▲图4 XML 响应页面
不过,一些浏览器可能以更结构化的方式显示已返回的文本。如果您想查看原始的源 XML,则您可以选择 View - Source 以便看到类似 图 5 中的窗口。
▲图5 XML 响应页面源代码
如您所见,该脚本创建了一些良好格式化的 XML,准备好供新的客户端代码使用。
解析 XML(而不是直接使用 HTML)的新客户端代码位于 清单 7 中。
清单 7. 使用 XML 的即时搜索页面
<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 对象符号)。
迁移到 JSON
在 Web 2.0 的世界中,JSON 是一种非常流行的移动数据的方式。它小巧、方便、快捷,便于浏览器读取,因为需要做的所有操作就是评估已返回的 JavaScript 代码。创建 JSON 也很简单,就如您在 清单 8 中的 Ajax 搜索页面的 JSON 版本中所见的那样。
清单 8. JSON Ajax 页面
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 中的响应页面。
▲图6 JSON 响应页面
虽然本页面可能不会太吸引人们的目光,但是对于浏览器中的 JavaScript 解译器来说,此页面看起来非常易于阅读。
用于读取 JSON 格式化输出的相应的即时 UI web 代码位于 清单 9 中。
清单 9. JSON 即时搜索 UI
<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 这样的标准工具,这些行为的初步阶段并不难实现。
我希望您能够在您的项目中使用本文所提供的代码。如果您这样做了请告之我。我会很高兴看到您是如何使用它的。