我们大家都喜欢评定内容的等级。我认为这是人类与生俱来的秉性。我和我的女儿都喜欢去电影院看电影;虽然她过去对电影的内容毫不讲究,不过现在却挑剔了许多,因为她已经四岁了。我曾经教过她使用 “大拇指朝上” 和 “大拇指朝下” 的评级系统 à la Ebert 和 Roeper。(她曾经为 Shrek the Third 这部电影竖起了大拇指。)我认为这就是人们乐于在因特网上评论产品和文章的原因所在。
本文将介绍如何结合使用 MySQL、PHP、Prototype.js 和 Asynchronous JavaScript™ and XML (Ajax) 在站点中添加简单的评级和评论功能。
虽然我女儿乐于使用大拇指进行评级,不过大多数人更倾向于另一种略有差别的评级系统:五星级评级系统,其中一颗星为最差,三颗星为中等。在本例中,我将使用五星级评级系统对电影进行评级。您也可以将本文中评级系统应用于需要的内容 — 如文章、产品和播客。
清单 1 显示了本例所使用的模式,其中创建了一个初始的 movies 表。这相当简单:在表中定义一个自动递增的电影 ID 和电影名称。然后,创建一个 rating 表保存电影的投票结果。这个表通过 movie_id 与 movie 表绑定在一起,表中还包含一个 rating 字段,取值范围为 1 到 5。
清单 1. rating.sql
DROP TABLE IF EXISTS movies;
CREATE TABLE movies (
movie_id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR( 128 ) NOT NULL,
PRIMARY KEY ( movie_id )
);
DROP TABLE IF EXISTS ratings;
CREATE TABLE ratings (
movie_id INTEGER NOT NULL,
rating INTEGER NOT NULL
);
|
如果要执行 “一人一投” 的模式,则必须在 ratings 表中添加一个用户 ID,从而确保每个人只能对一部电影评定一次。考虑到程序的简单性,我并没有实现这一功能。
要将模式导入数据库,必须先创建一个数据库,然后再添加模式。可以使用以下命令行指令完成这些操作:
% mysqladmin create comments % mysql comments < ratings.sql |
您可能需要根据您的 MySQL 安装添加登录凭证。
完成这些步骤之后,我们需要使用一个页面显示可用的电影,并为每部电影添加一个评级页面链接。清单 2 显示了这个索引页面。
清单 2. index.php
<html>
<body>
<?php
require_once("DB.php");
$db =& DB::Connect( 'mysql://root@localhost/comments', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( 'SELECT * FROM movies' );
while( $res->fetchInto( $row ) )
{
?>
<a href="rate.php?id=<?php echo($row[0]) ?>"><?php echo($row[1]) ?></a><br/>
<?php
}
?>
</body>
</html>
|
简单起见,我使用结构化查询语句(Structured Query Language,SQL)语句在 movies 表中加入了一些著名的电影。下载 部分提供了示例代码。您可以在索引页面中看到这些电影,如 图 1 所示。
图 1. 电影列表
如果您无法通过示例代码显示出这个清单,可能您的机器未使用 PEAR 存储库安装 DB 模块。要安装它,只需在命令行中运行以下命令:
% pear install DB |
现在,电影列表可以正常显示了。接下来,我们将实现电影评级页面,这个页面还可以显示当前投票的总排名和日平均排名。实现这些功能的 rate.php 页面代码如 清单 3 所示。
清单 3. rate.php
<?php
require_once("DB.php");
$db =& DB::Connect( 'mysql://root@localhost/comments', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$id = $_GET['id'];
$title = '';
$res = $db->query( 'SELECT name FROM movies WHERE movie_id=?', array( $id ) );
while( $res->fetchInto( $row ) ) { $title = $row[0]; }
?>
<html>
<head>
<title><?php echo($title); ?></title>
<script src="prototype.js"></script>
<script>
function rate( value ) {
new Ajax.Updater( 'rating', 'ratemovie.php?id=<?php echo($id)?>&v='+value );
}
</script>
</head>
<body>
<h1><?php echo($title); ?></h1>
<div id="rating">
<img src="star_off.gif" onclick="rate(1)"></img>
<img src="star_off.gif" onclick="rate(2)"></img>
<img src="star_off.gif" onclick="rate(3)"></img>
<img src="star_off.gif" onclick="rate(4)"></img>
<img src="star_off.gif" onclick="rate(5)"></img>
<br/><br/>
<?php
$res2 = $db->query(
'SELECT count( rating ), sum(rating ) FROM ratings WHERE movie_id=?',
$id
);
while( $res2->fetchInto( $row ) )
{
?>
Votes: <?php echo($row[0]); ?><br/>
Average Rating: <?php echo($row[1]/$row[0]); ?>
<?php
}
?>
</div>
</body>
</html>
|
文件顶部的代码通过传递 ID 参数获得电影标题。在脚本中间部分,我们导入了 prototype.js 库并创建了一个 rate() JavaScript 函数,这个函数将通过 Ajax 使用 Prototype 库调用 ratemovie.php 页面。然后,我们添加了一组星形图像,点击这些图像时脚本将调用 rate() 函数。
在文件底部的代码中,我们运行了一个快速查询获取投票数和指定电影的投票总数。然后,在页面中显示出这些数据,这样读者就可以知道该电影的评级情况。
清单 4 中的 ratemovie.php 脚本负责在数据库中添加评级,然后返回一些超文本标志语言(Hypertext Markup Language,HTML)代码,以替换原始页面中的投票星级和评论数。
清单 4. ratemovie.php
<?php
require_once("DB.php");
$db =& DB::Connect( 'mysql://root@localhost/comments', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$v = $_GET['v'];
$id = $_GET['id'];
$sth = $db->prepare( 'INSERT INTO ratings VALUES ( ?,? )' );
$db->execute( $sth, array( $id, $v ) );
?>
<img src="star_<?php echo( ($v>0)?'on':'off' ) ?>.gif"></img>
<img src="star_<?php echo( ($v>1)?'on':'off' ) ?>.gif"></img>
<img src="star_<?php echo( ($v>2)?'on':'off' ) ?>.gif"></img>
<img src="star_<?php echo( ($v>3)?'on':'off' ) ?>.gif"></img>
<img src="star_<?php echo( ($v>4)?'on':'off' ) ?>.gif"></img>
<br/><br/>
<?php
$res2 = $db->query(
'SELECT count( rating ), sum(rating ) FROM ratings WHERE movie_id=?',
$id
);
while( $res2->fetchInto( $row ) )
{
?>
Votes: <?php echo($row[0]); ?><br/>
Average Rating: <?php echo($row[1]/$row[0]); ?>
<?php
}
?>
|
图 2 显示了运行中的评级页面。
图 2. 评级页面
单击页面的中星形图案时,投票将添加到数据库中;星形图案、投票和平均评级的 HTML 代码会发生改变,从而反映出投票已添加。
理想情况下,我希望能够在主页上看到所有电影的评分情况。清单 5 中的新版索引页面实现了这一功能。
清单 5. index2.php
<html>
<body>
<table>
<?php
require_once("DB.php");
$db =& DB::Connect( 'mysql://root@localhost/comments', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( 'SELECT * FROM movies' );
while( $res->fetchInto( $row ) )
{
$res2 = $db->query(
'SELECT count( rating ), sum(rating ) FROM ratings WHERE movie_id=?', $row[0]
);
$rating = 0.0;
while( $res2->fetchInto( $row2 ) ) { $rating = $row2[1] / $row2[0]; }
?>
<tr><td align="center">
<?php echo( $rating > 0 ? $rating : 0 ) ?>
<td><td>
<a href="rate2.php?id=<?php echo($row[0]) ?>"><?php echo($row[1]) ?></a>
</td></tr>
<?php
}
?>
</table>
</body>
</html>
|
从 图 3 中可以看出,各个电影的评级情况都在新版的索引页面中显示出来了。
图 3. 更新后的电影页面
使用 Ajax、PHP、MySQL 和非常方便的 Prototype.js JavaScript 库实现投票模式就这么简单。
在下一个示例中,我们将实现电影评论功能。
Web 上的评论系统形式各异,从极其简单的博客评论系统(可以对博客条目发表一连串的评论)到异常复杂的主题评论系统(比如 Slashdot 上的评论系统),不一而足。
本例所使用的评论系统比较简单。您可以根据需要对它进行适当的调整。
首先,在已有数据库模式中添加一个表,如 清单 6 所示。
清单 6. comments.sql
DROP TABLE IF EXISTS comments;
CREATE TABLE comments (
movie_id INTEGER NOT NULL,
email VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
comment TEXT NOT NULL
);
|
所添加的是 comments 表,这个表通过 movie_id 字段与电影关联在一起。表中定义了一个电子邮件地址、评论者名称和评论文本。这是一个相当基本的评论系统,类似于 WordPress 和 MoveableType 博客软件上的评论系统。
如果想要创建一个主题评论系统,则需要在表中添加一个自动递增的 ID 字段,然后使用一个可为空的 parent_id 字段指向各个评论的父级评论。如果该 字段为空,则这个评论是顶层评论。
要在评级页面中加入评论功能,我们需要在页面底部包含一些额外的脚本。清单 7 显示了这个新的 PHP 代码。
清单 7. rate2.php
...
<h2>Comments</h2>
<div id="comments">
<?php
$res3 = $db->query(
'SELECT * FROM comments WHERE movie_id=?',
$id
);
while( $res3->fetchInto( $row3 ) )
{
?>
<div>
<a href="mailto:<?php echo($row3[1]) ?>"><?php echo($row3[2]) ?></a> says:
'<?php echo($row3[3]) ?>'
</div>
<?php
}
?>
</div>
<div style="margin-top:20px;">Add your own comment:</div>
<form id="cform">
<input type="hidden" name="id" value="<?php echo($id)?>">
<table>
<tr><td>Name:</td><td><input type="text" name="name"></td></tr>
<tr><td>Email:</td><td><input type="text" name="email"></td></tr>
<tr><td>Comment:</td><td><textarea name="comment" id="comment_text"></textarea></td></tr>
</table>
</form>
<button onclick="addcomment()">Add Comment</button>
<script>
function addcomment()
{
new Ajax.Updater( 'comments', 'addcomment.php',
{
method: 'post',
parameters: $('cform').serialize(),
onSuccess: function() {
$('comment_text').value = '';
}
} );
}
</script>
</body>
</html>
|
脚本首先使用数据库中关于该电影的评论填充 “comments” <div> 标记。 然后,在标准 HTML <form> 标记中包含评论名称、评论者电子邮件和评论内容的字段。form 标记中还包含一个隐藏值(当前所查看电影的 ID),从而评论添加脚本知道应该将这个评论指派给哪部电影。
表单下面的 Add Comment 按钮将调用 addcomment() JavaScript 函数。这个函数使用了 Prototype.js 库中的 Ajax.Updater 对象来调用 addcomment.php 脚本。它使用了非常方便的 serialize() 函数对名称、电子邮件地址和评论内容进行打包,这个函数也是由 Prototype.js 提供的。
如果评论成功,代码会重置评论字段的文本,这样可以允许用户随意添加任意数量的评论,而不用重新输入名称和电子邮件地址。
清单 8 显示了 addcomment.php 脚本。
清单 8. addcomment.php
<?php
require_once("DB.php");
$id = $_POST['id'];
$db =& DB::Connect( 'mysql://root@localhost/comments', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$sth = $db->prepare( 'INSERT INTO comments VALUES ( ?, ?, ?, ? )' );
$db->execute( $sth, array( $id,
$_POST['email'], $_POST['name'], $_POST['comment' ] ) );
$res = $db->query('SELECT * FROM comments WHERE movie_id=?', $id );
while( $res->fetchInto( $row ) )
{
?>
<div>
<a href="mailto:<?php echo($row[1]) ?>"><?php echo($row[2]) ?></a> says:
'<?php echo($row[3]) ?>'
</div>
<?php
}
?>
|
首先,脚本将 POST 数据中指定的评论内容添加到数据库中。然后,将所有的评论内容作为 HTML 代码输出,其方式与原始评级页面一样,只不过这次页面中额外显示了用户所提供的评论。
新的评级页面如 图 4 所示。
图 4. 更新后的评级页面,具有评论功能。
这个评论系统可以即时地向用户反馈信息。用户单击 Add Comment 时,页面会立即更新评论内容。系统还可以显示出其他用户在这期间提交的评论。
您可能需要扩展这个示例,使服务器可以自动更新评论部分。当然,您大可不必调用 addcomment.php 来完成这点。可以使用另一个脚本来返回评论,而不必添加新脚本。为此,Prototype.js 提供了一个 Ajax.PeriodicalUpdater 类,使用这个类中定义的 ID、刷新率和 URL 可以根据需要刷新 Web 页面中的任何部分。
扩展该示例的另一种简单的方法是将电影列表的内容和排名作为 RSS 导出。实现这一功能的代码如 清单 9 所示。
清单 9. rss.php
<?php
header( "content-type:text/xml" );
?>
<rss version="0.91">
<channel>
<title>Movie rankings</title>
<link>http://localhost/comments/rss.php</link>
<?php
require_once("DB.php");
$db =& DB::Connect( 'mysql://root@localhost/comments', array() );
if (PEAR::isError($db)) { die($db->getMessage()); }
$res = $db->query( 'SELECT * FROM movies' );
while( $res->fetchInto( $row ) )
{
$res2 = $db->query(
'SELECT count( rating ), sum(rating ) FROM ratings WHERE movie_id=?', $row[0]
);
$rating = 0.0;
while( $res2->fetchInto( $row2 ) ) { if ( $row2[0] > 0 ) $rating = $row2[1] / $row2[0]; }
?>
<item>
<title><?php echo($row[1]) ?> -
<?php echo( $rating > 0 ? $rating : 0 ) ?> stars</title>
<link>http://localhost/commentsts/rate2.php?id=<?php echo($row[0]) ?></link>
<description><?php echo($row[1]) ?></description>
</item>
<?php
}
?>
</channel>
</rss>
|
清单 9 中的代码的作用类似于使用 HTML 格式导出数据。它使用 <title>、<description> 和 <link> 标记指向各个电影页面,而不是 HTML 中的 <table>、<tr> 和 <td> 标记。当我在 Firefox 浏览器中打开这个页面时,所显示的页面如 图 5 所示。
图 5. 浏览器中的 RSS 提要
很容易吧!从 PHP 中获取 XML 提要就是这么简单。
当我使用以下命令行在本地运行代码时:
% php rss.php
|
我可以直接看到 RSS XML。清单 10 显示了这个 RSS 的一部分。
清单 10. RSS excerpt
<rss version="0.91">
<channel>
<title>Movie rankings</title>
<link>http://localhost/comments/rss.php</link>
<item>
<title>Star Wars - 4.5 stars</title>
<link>http://localhost/commentsts/rate2.php?id=1</link>
<description>Star Wars</description>
</item>
<item>
...
|
最近,关于用户生成内容的话题格外引人关注,尤其是它如何增强 Web 2.0 的力量这方面。与本文中示例一样,您可以使用 Prototype.js 库之类的优秀工具轻易地构建 Ajax 应用程序。对网站内容的评级和评论功能确实是一种极好的用户生成内容的形式。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 本文的源代码 | x-ajaxxml5-code.zip | 22 KB | HTTP |
学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文。
-
参阅本系列的其他部分
- Ajax 和 XML: 五个很酷的 Ajax 小部件随着 Web 2.0 浪潮的到来,用户体验得到了全新的关注。本文介绍了五个可用于增强站点交互性的 Ajax 小部件。
- Ajax 和 XML: 五种常见 Ajax 模式Ajax 应用程序中哪一种常见架构模式应用最广泛呢?本文将介绍五种常见 Ajax 设计模式,可以使用它们作为工作的基础。
- Ajax 和 XML: 五种 Ajax 反模式通过理解错误的编码方式,可以更好地了解如何正确地进行编码。本文将讨论一些需要避免的常见 Ajax 编码实践。
- Ajax 和 XML: 借鉴最优秀的 Ajax 应用程序跟随本文一起游览 Web 2.0 世界中的一些顶尖的 Ajax 应用程序。探索这些应用程序在用户层面上获得成功的秘决。
-
Ajax 技术资源中心:developerWorks 上所有有关 Ajax 的问题都可以在这里找到解答。
-
订阅 Ajax 相关文章和教程 的 RSS 提要:获得即将发表的 Ajax 相关文章和教程的通知(查看 developerWorks 内容 RSS 提要 了解更多的信息)。
-
PHP 主页:PHP 程序人员的宝库,其中含有这个广泛使用的脚本语言的各方面信息。
-
Prototype
库:这个 JavaScript 框架可以简化动态 Web 应用程序的开发过程。
- The Scriptaculous
JavaScript 库:获取有用的显示助手程序和显示效果,使用这个基于 Prototype 的框架开发动态的 Web 站点。
-
Prototype.js
文档页面:获取有关 Prototype JavaScript 库的更多信息,该页面含有到官方 Prototype 博客和其他资源的链接。
-
jQuery:这个 JavaScript 库提供了与 Prototype.js 类似的功能。
-
Yahoo UI
库:查阅 Yahoo! 的 Ajax 工具包。
-
developerWorks 中国网站 XML 专区:在 developerWorks XML 中学习有关 XML 的方方面面。
-
IBM XML 认证:看看如何才能成为一名 IBM 认证的 XML 及相关技术的开发人员。
-
XML 技术文档库:developerWorks XML 专区提供了大量技术文章、提示、教程、标准以及 IBM 红皮书。
-
developerWorks 技术活动和网络广播:随时关注技术的最新进展。
-
Ajaxian:研读有关 Ajax 开发的文章,了解最新的动向和使用 Ajax 的前端小部件。
获得产品和技术
- IBM®
DB2® Enterprise 9:下载 DB2 9 试用版或 DB2 Express-C 9,免费版的 DB2 Express 9 数据服务器。
讨论
-
XML 专区讨论论坛:参与任何以 XML 为中心的论坛。
Jack D. Herrington 是一位有着超过 20 年经验的高级软件工程师。他是 Code Generation in Action、Podcasting Hacks 和 PHP Hacks 这三本书的作者。他还发表了 30 多篇文章。可以通过 jherr@pobox.com 与 Jack 联系。