БлогNot. Пишем простой "рейтинг со звёздочками" для сайта

Пишем простой "рейтинг со звёздочками" для сайта

На многих сайтах вы видели систему "рейтинга со звёздочками", позволяющего вашим посетителям легко оценить тот или иной материал одним кликом мыши.

Напишем собственную, по возможности, простейшую форму такого рейтинга.

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

вид рейтинга со звёздочками в браузере
вид рейтинга со звёздочками в браузере

Создадим на локальном хосте папку stars для файлов скрипта. Весь код будем писать в файле index.html в кодировке Юникода UTF-8 с таким вот обрамлением HTML5:

<!DOCTYPE html>
<html lang="ru-RU">
<head>
 <meta charset="utf-8">
 <title>Звездочки</title>
 <style>
 </style>
</head>
<body>
</body></html>

Это корректный код с точки зрения стандартного валидатора разметки. На самом деле кусочки HTML- и Javascript-кода, которые мы сейчас напишем, могут быть включены куда угодно в ваши файлы типа .html или .php.

Перед закрывающей частью </head> подключим библиотеку JQuery. Для нашей цели хватит возможностей первой её версии, так что не будем гнаться за самой новой:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

Внутри тега <body> поместим форму для добавления звёздочек и вывода текущего рейтинга материала:

<div id="rating">
 <div class="comment" id="message">Оцените этот материал:</div>
 <div>
  <div class="stars" id="db_1"></div>
  <p class="progress" id="progress_id"></p>
 </div>
 <div class="rating" id="rating_id">5.0</div>
</div>

Важно, что в реальном скрипте идентификатор db_1 должен формироваться программно и получать после префикса db_ номер записи (идентификатор) некоторой страницы, на которой расположен рейтинг и которая сейчас оценивается.

То есть, id этого элемента получает, например, значение db_999 для записи моего блога с идентификатором 999.

Как правило, при использовании базы данных идентификатор записи Вы уже получали, формируя текущую страницу, если предположить, что он хранится в переменной с именем $id, то строчка последнего листинга изменится на

<div class="stars" id="db_<?php echo $id; ?>"></div>

Внутри тега <style> опишем, как должны выглядеть элементы, его можно поместить сразу после <title>:

 <style>
  #rating {
   width: 400px;
   height: 32px;
   border-radius: 6px;
   box-shadow: 0 0 1px 1px #999999;
   margin: 8px auto;
   padding: 8px;
   text-align: center;
  }
  #rating div { float: left; }
  #rating p { margin: 0; padding: 0; }
  #rating_id { font-size: 14px; text-align: right; }
  .comment {
   width: 180px;
   font-size: 14px;
   margin: 0 14px 0 0;
   text-align: right;
  }
  .comment, .rating { 
   line-height: 28px; 
  }
  .stars { 
   background: url(stars.png);
  } 
  .stars, .progress {
   width: 130px;
   height: 28px;
   cursor: pointer;
  }
  .progress { 
   background: #FFFF00; 
  }
  .rating {
   margin: 0 0 0 14px;
  }
 </style>

Здесь многие размеры подогнаны не очень тщательно, можно сделать лучше. Сами пять звёздочек будут фоновой картинкой, расположенной в папке приложения на локальном хосте. "Внутренность" звёздочек при этом прозрачна, а фон изображения - белый.

картинка со звёздочками, файл stars.png
картинка со звёздочками, файл stars.png

За картинкой мы поместим абзац с id="progress_id" и жёлтым фоном, а потом будем динамически менять ширину этого абзаца в ситуациях, когда мышь находится поверх звёздочек.

Клик по звёздочкам вызовет выполнение серверного скрипта, где наш выбор будет либо сохранён в базе, либо проигнорирован, если мы уже голосовали.

Внешний вид скрипта готов, можно выполнить в браузере ссылку http://localhost/stars/ и увидеть это.

Теперь добавим логику приложения, которая будет реализована в виде яваскрипта, его также можно добавить перед </head> после тега подключения JQuery:

 <script>
  $(document).ready(function() {

   function move (e, obj) {
    var progress = e.pageX - obj.offset().left;
    var rating = progress * 5 / $('.stars').width();
    obj.next().width(progress);
    var d = rating - Math.floor(rating), r;
    if (d<=1/3) r =  Math.floor(rating);
    else if (d<=2/3) r = Math.floor(rating) + 0.5;
    else r = Math.ceil(rating);
    $('#rating_id').text(r);
   }

   $('#rating .stars').on('mousemove', function(e){
    if ($(this).hasClass('fixed')==false) move(e, $(this));
   });

   $('#rating .stars').click (function(e) {
    $(this).toggleClass('fixed');
    move (e, $(this));
    var rt = parseFloat($('#rating_id').text());
    jQuery.post('rating.php', {
     id: $(this).attr('id').substr(3), //id записи
     rating: rt //рейтинг юзера
    }, notice); //по завершении выполнить notice
    $('#rating_id').text('');
   });

   function notice (text) {
    var arr = text.split('#');
    $('#message').fadeOut(500, function() { 
     $(this).text(arr[0]); //сообщение
     $('#rating .stars').hide(); //убрать звездочки
     $('#rating #progress_id').hide(); //и фон
     $('#rating_id').text('Всего '+arr[2]+', '+arr[1]+' из 5'); //общий счет
    }).fadeIn(1500);
   }
  });
 </script>

Как видно из листинга, при клике на выбранном количестве звёздочек скрипт обратится к серверной части rating.php, которая зафиксирует выбор пользователя в базе данных и покажет среднюю текущую оценку материала.

Шаг по оценке равен 0.5, то есть, можно выставить 0 баллов, 0.5, 1, и так далее до 5.

Прежде, чем двигаться дальше, запустим сервер баз данных ( в Denwer - ссылка http://localhost/tools/phpmyadmin ) и создадим в любой удобной для нас базе две таблицы, которые понадобятся нашему серверному скрипту.

Имена таблиц будут starvotes и starballs, а кодировка базы может быть любой, ведь все поля - цифровые.

Кроме того, вторая таблица необязательна, вы можете просто добавить столбцы points и votes в ту таблицу, откуда берутся оцениваемые материалы и их идентификаторы, такие как db_1 выше в коде.

Вот SQL-запрос, который нужно выполнить к выбранной базе:

CREATE TABLE `starvotes` (
 `id` int(10) unsigned NOT NULL auto_increment,
 `date` int(10) unsigned NOT NULL,
 `dbid` int(10) unsigned NOT NULL,
 `ip` int(10) unsigned NOT NULL,
 `rating` float unsigned NOT NULL,
 PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1;

CREATE TABLE `starballs` (
 `id` int(10) unsigned NOT NULL auto_increment,
 `dbid` int(10) unsigned NOT NULL,
 `points` float unsigned NOT NULL,
 `votes` int(10) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1;

Наконец, напишем серверный скрипт rating.php и разместим его в папке приложения.

Чтобы не переполнять базу, "очень старые" записи будем стирать, за это отвечает настройка DAYS_LIMIT_TO_REPEAT, в остальном имеем обычный скрипт для работы с БД. Вот его полный листинг, разумеется, на реальном хостинге настройки подключения к базе данных придётся поменять (для удобства они собраны в начале файла в виде констант).

<?php
 define ('DB_HOST','localhost'); //имя хоста
 define ('DB_LOGIN','root'); //логин
 define ('DB_PASSWORD',''); //пароль
 define ('DB_NAME','test'); //имя БД
 define ('VOTES_NAME','starvotes'); //имя таблицы с голосами
 define ('BALLS_NAME','starballs'); //имя таблицы с баллами за статьи
 define ('DB_ENCODING','utf8'); //кодировка БД
 define ('DAYS_LIMIT_TO_REPEAT','10'); //через сколько суток можно голосовать заново

 if (!isset($_POST['id']) or !is_numeric($_POST['id']) or 
     !isset($_POST['rating']) or !is_numeric($_POST['rating'])) {
  die('Неверный вызов PHP [1]');
 }
 $dbid = intval($_POST["id"]);
 $myball = floatval($_POST["rating"]);
 if ($dbid <= 0 or $myball < 0 or $myball > 5) {
  die('Неверный вызов PHP [2]');
 }

 $time = $_SERVER['REQUEST_TIME'];
 $ip = get_ip();
 $mysql_id = mysqli_connect (DB_HOST,DB_LOGIN,DB_PASSWORD,DB_NAME)
  or die (sprintf("Ошибка подключения: %s\n", mysqli_connect_error()));
 mysqli_set_charset ($mysql_id, DB_ENCODING) or 
  die (sprintf("Ошибка установки кодировки: %s\n", mysqli_error($mysql_id)));
 mysqli_select_db ($mysql_id, DB_NAME) or
  die (sprintf("Ошибка выбора таблицы: %s\n", mysqli_error($mysql_id)));

 $sql = 'DELETE FROM '.VOTES_NAME.' WHERE date<'.($time-DAYS_LIMIT_TO_REPEAT*86400);
 $res = mysqli_query($mysql_id,$sql) or die ('Ошибка SQL: ['.$sql.']; '.mysqli_error($mysql_id)); 
   //Удалили записи старше DAYS_LIMIT_TO_REPEAT дней
 $sql = 'SELECT count(id) FROM '.VOTES_NAME.' WHERE dbid='.$dbid.' and ip=INET_ATON(\''.$ip.'\')';
 $res = mysqli_query($mysql_id,$sql) or die ('Ошибка SQL: ['.$sql.']; '.mysqli_error($mysql_id));
 $number = mysqli_fetch_array ($res);
 $result = ''; 
 $updater = 0;

 if ($number[0] == 0) {
  $sql = 'INSERT INTO '.VOTES_NAME.' (date,dbid,ip,rating) values ('.
   $time.','.$dbid.',INET_ATON(\''.$ip.'\'),'.$myball.')';
  $res = mysqli_query ($mysql_id, $sql) or die ('Ошибка SQL: ['.$sql.']; '.mysqli_error($mysql_id));
  $result .= 'Голос принят';
  $updater = 1;
 }
 else {
  $result .= 'Вы уже голосовали';
 }

 if ($updater) {
  $sql = 'SELECT dbid,points,votes FROM '.BALLS_NAME.' WHERE dbid='.$dbid;
  $res1 = mysqli_query ($mysql_id,$sql) or die ('Ошибка SQL: ['.$sql.']; '.mysqli_error($mysql_id));
  if (mysqli_num_rows ($res1)) {
   $sql = 'UPDATE '.BALLS_NAME.
    ' SET points=(points+'.$myball.'),votes=(votes+1) WHERE dbid='.$dbid.' LIMIT 1';
   $res = mysqli_query ($mysql_id,$sql) or die ('Ошибка SQL: ['.$sql.']; '.mysqli_error($mysql_id));
  }
  else {
   $sql = 'INSERT INTO '.BALLS_NAME.' (dbid,points,votes) values ('.$dbid.','.$myball.',1)';
   $res = mysqli_query ($mysql_id,$sql) or die ('Ошибка SQL: ['.$sql.']; '.mysqli_error($mysql_id));
  }
 }
 $sql = 'SELECT dbid,points,votes FROM '.BALLS_NAME.' WHERE dbid='.$dbid;
 $res1 = mysqli_query ($mysql_id,$sql) or die ('Ошибка SQL: ['.$sql.']; '.mysqli_error($mysql_id));
 $row = mysqli_fetch_assoc ($res1);
 $ball = round ($row['points'] / $row['votes'],2);
 
 echo "$result#$ball#".$row['votes'];

 function get_ip () {
  if( getenv('HTTP_X_FORWARDED_FOR') != '' ) {
   $client_ip = ( !empty($_SERVER['REMOTE_ADDR']) ) ? $_SERVER['REMOTE_ADDR'] : 
    ( ( !empty($_ENV['REMOTE_ADDR']) ) ? $_ENV['REMOTE_ADDR'] : $REMOTE_ADDR );
   $entries = explode(',', getenv('HTTP_X_FORWARDED_FOR'));
   reset($entries);
   foreach ($entries as $entrykey=>$entry) {
    $entry = trim($entry);
    if ( preg_match("/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/", $entry, $ip_list) ) {
     $private_ip = array('/^0\./', '/^127\.0\.0\.1/', '/^192\.168\..*/', 
      '/^172\.((1[6-9])|(2[0-9])|(3[0-1]))\..*/', '/^10\..*/', '/^224\..*/', '/^240\..*/');
     $found_ip = preg_replace($private_ip, $client_ip, $ip_list[1]);
     if ($client_ip != $found_ip) {
      $client_ip = $found_ip;
      break;
     }
    }
   }
  }
  else {
   $client_ip = ( !empty($_SERVER['REMOTE_ADDR']) ) ? $_SERVER['REMOTE_ADDR'] : 
    ( ( !empty($_ENV['REMOTE_ADDR']) ) ? $_ENV['REMOTE_ADDR'] : $REMOTE_ADDR );
  }
  return $client_ip;
 }
?>

Вы можете посмотреть, что у нас получилось, по ссылке, а также использовать эти наработки для своих скриптов, которые нужно сделать ещё умнее и красивей :)

Чтобы не было проблем с Юникодом на Денвере, не забудем добавить в папку скрипта волшебный файл .htaccess

 Этот скрипт в работе

Ну а я себе в блог добавлять такой рейтинг не буду, не хочу каждый раз подгружать JQuery :) Впрочем, помните, что всегда можно переписать код без него, это всего лишь библиотека, а не язык программирования.

31.01.2019, 16:15 [9969 просмотров]


теги: программирование javascript php mysql jquery рейтинг

показать комментарии (8)