Ajax и XML: Применение Ajax для создания рейтингов и комментариев (исходники)

Джек Д Херрингтон, главный инженер-программист, Leverage Software Inc.

Все мы любим оценивать. Я думаю, это заложено в наших генах. Мы с дочерью любим ходить в кино; раньше ей нравилось все подряд, но теперь, когда ей исполнилось четыре года, она стала значительно разборчивей. Я научил ее использовать систему рейтингов "хорошо" и "плохо" (большой палец вверх и вниз) как в программе Ebert and Roeper. (Она дала Шреку Третьему оценку «палец вверх».) Я думаю, именно поэтому в Интернете так популярны оценки и рецензирование продуктов, статей и т.п.

В этой статье показывается, как с помощью сочетания MySQL, PHP, Prototype.js и асинхронного JavaScript и XML (Ajax) добавить на любой сайт простые функции оценки и комментирования.

Система рейтингования

Хотя моя дочь использует оценки «хорошо» и «плохо», я понимаю, что большинство людей используют несколько более сложную систему: пятизвездную шкалу, в которой одна звезда обозначает «очень плохо», пять звезд - «очень хорошо», а три звезды - «средне». В этом примере я буду применять пятизвездную систему для оценки фильмов. Вы можете спокойно заменить мои фильмы всем, что вашей душе угодно - статьями, продуктами или подкастами.

В листинге 1 показана схема для этого примера, начиная с таблицы для фильмов. Она очень проста: идентификатор фильма с автоинкрементом и название фильма. Все голоса за фильмы хранятся в таблице рейтингов. Эта таблица связывается с таблицей фильмов посредством movie_id и содержит всего одно дополнительное поле - оценку, выраженную числом от 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 идентификатор пользователя, а также сделать так, чтобы один пользователь мог отдать за один фильм только один голос. Я хотел сделать этот пример более простым, поэтому опустил эту часть.

Чтобы реализовать эту схему в базе данных, нужно сначала создать базу данных, а потом - добавить в нее схему. Это можно сделать, выполнив в командной строке следующие команды:

% 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>

Чтобы облегчить себе работу, я добавил в таблицу movies несколько известных фильмов с помощью SQL-запроса, который вы можете загрузить из раздела Материалы для загрузки . Список фильмов на индексной странице показан на рисунке 1.

Рисунок 1. Список фильмов
Список фильмов

Если в результате выполнения приведенного кода вы не получили этого списка, скорее всего, у вас не установлен модуль баз данных DB из хранилища PEAR. Чтобы установить его, просто запустите в командной строке следующую команду:

% pear install DB

Теперь, когда мы показали список фильмов, настало время создать страницу, которая позволит вам оценивать фильмы и выводить суммарные данные по уже отданным голосам и среднее значение рейтинга. В листинге 3 показано, как все это можно сделать на странице rate.php.

Листинг 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>

В самом начале файла мы получаем название фильма по полученному в качестве параметра идентификатору. В средней части сценария мы включаем файл библиотеки prototype.js и создаем функцию JavaScipt rate(), которая вызывает страницу ratemovie.php посредством Ajax, используя библиотеку Prototype. После этого добавляем несколько изображений со звездами, при нажатии каждой из которых вызывается функция rate().

В нижней части файла мы запускаем простой запрос для получения количества и суммы голосов для этого фильма. После этого мы выводим эти данные, чтобы дать читателю знать, как оценивают этот фильм.

Сценарий ratemovie.php, приведенный в листинге 4, выполняет добавление оценки в базу данных, после чего возвращает фрагмент 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 и крайне удобной библиотеки JavaScript Prototype.js.

В следующей части примера мы реализуем комментарии или рецензии для фильмов.

Комментирование

В Интернете существует множество систем комментирования, от простейших - как в большинстве блогов, где имеется просто строка комментариев к записи блога - до интеллектуальных систем разветвленных комментариев, пример которых можно увидеть на 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.

Если вы хотите создать древовидную систему комментирования, вам нужно будет добавить к таблице идентификатор с автоинкрементом, после чего добавить поле 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>

Сценарий начинает с заполнения тега <div> "comments" текущими комментариями к этому фильму из базы данных. После этого указывается стандартный HTML-тег <form>, содержащий поля для имени комментатора, адреса его электронной почты и собственно комментария. В форме также содержится скрытое значение (идентификатор просматриваемого на данный момент фильма), чтобы сценарий, добавляющий комментарий, знал, какому фильму этот комментарий сопоставить.

Кнопка Add Comment, расположенная под формой, вызывает функцию JavaScript addcomment(). Эта функция через объект Ajax.Updater библиотеки Prototype.js вызывает сценарий 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, который - по заданному идентификатору, частоте обновления и URL - будет обновлять любую часть Web-страницы с указанной частотой.

Добавление каналов RSS

Другой простой способ расширения этого примера - экспорт списка фильмов с оценками в виде канала 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. Вместо тегов <table>, <tr> и <td> я использовал теги <title>, <description> и <link>, указывающие на страницу каждого фильма. Когда я перехожу на эту страницу в браузере Firefox, я вижу что-то похожее на рисунок 5.

Рисунок 5. Канал RSS в браузере
Канал RSS в браузере

Все достаточно просто. Получить каналы XML из PHP несложно.

Когда я запускаю этот код локально, используя следующую команду:

% php rss.php

я могу видеть непосредственно XML-код канала RSS. Фрагмент этого RSS-канал показан в листинге 10.

Листинг 10. Фрагмент RSS

                
<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. Как мы увидели из примеров, приведенных в этой статье, можно с легкостью создавать приложения Ajax с помощью таких замечательных инструментов, как библиотека Prototype.js. Функции, которые дают посетителям возможность оценивать и комментировать материалы вашего сайта, предоставляют вам отличный контент, формируемый пользователями.


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=7542