Когда планируется внести изменения на веб сайт, всегда полезно услышать, что думают посетители о новых функциях и особенностях. Очень долго разработчики были ограничены только использованием контактной формы, в надежде на то, что обратная связь с пользователем сработает, что, к сожалению, не всегда приносило результат.
Сегодня мы изменим подход - будем использовать социальный принцип, который принес успех многим известным сайтам, и, к томуже, очень нравится посетителям, которые с удовольствием оставляют советы и голосуют за предложения других по будущим изменениям сайта.
sourse
XHTML
Используем новый тип документа HTML5, определяем секцию head, вставляем тег title, основную таблицу стилей – styles.css .
demo.php
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Рекомендации на будущее (используем PHP, jQuery и MySQL) | RUSELLER.COM</title>
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body>
<div id="page">
<div id="heading" class="rounded">
<h1>Рекомендации на будущее <i>для RUSELLER.COM</i></h1>
</div>
<!-- Сгенерированный список предложений выводится здесь -->
<form id="suggest" action="" method="post">
<p>
<input type="text" id="suggestionText" class="rounded" maxlength="255" />
<input type="submit" value="Отправить" id="submitSuggestion" />
</p>
</form>
</div>
<p class="createdBy"><a href="http://ruseller.com/lessons.php?rub=37&id=688">Урок на сайте RUSELLER.COM</a></p>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="script.js"></script>
</body>
</html>
Затем переходим к тегу body и div #page, который является основным контейнером. Он содержит заголовок, неупорядоченный список всех предложений (который генерируется PHP) и формы отправки.
В завершении мы включаем библиотеку jQuery из CDN библиотеки Google AJAX и наш скрипт script.js, который будет описан детально в следующем разделе урока.
Рекомендации на будущее (используем jQuery, PHP и MySQL)
Структура таблиц базы данных
Наше приложение использует две таблицы MySQL для хранения данных. Suggestions и Suggestions_votes. Первая таблица содержит текст предложения и данные , такие как рейтинг и количество голосов, которые получило данное предложение. Вторая таблица хранит записи с IP адресом голосовавшего и предотвращает голосование более одного раза в день по пункту с одного и того же IP.
Структура таблицы Suggestions
Для ускорения запроса выборки данных индекс определяется по полю rating. Такой подход полезен, когда предложения упорядочены по популярности.
Таблица Suggestions_votes имеет основной ключ состоящий из трех полей – suggestion_id, IP, и date. Так как основной ключ не дает дублировать строки, мы можем быть уверенны, что пользователь может голосовать только один раз в день просто проверяя значение переменной affected_rows после операции вставки данных.
Suggestions_Votes Schema
Структура таблицы Suggestions_Votes
PHP
Перед тем, как генерировать пункт предложения и совершать действия AJAX, сначала нужно посмотреть на класс PHP suggestion . Он использует два магических метода PHP (помимо конструктора) для обеспечения функциональности нашего кода. Когда генерируется страница, PHP выполняет запрос MySQL к базе данных и создает объект данного класса для каждой строки таблицы. Столбцы в каждой строчке добавляются как свойства объекта.
suggestion.class.php
class Suggestion
{
private $data = array();
public function __construct($arr = array())
{
if(!empty($arr)){
// Массив $arr передается только тогда,
// когда мы вручную создаем объект данного класса в ajax.php
$this->data = $arr;
}
}
public function __get($property){
// Данный магический метод вызывается если мы
// получаем доступ к свойству, которое не существует.
if(array_key_exists($property,$this->data)){
return $this->data[$property];
}
return NULL;
}
public function __toString()
{
// Данный магический метод вызывается
// при конвертировании объекта в строку:
return '
<li id="s'.$this->id.'">
<div class="vote '.($this->have_voted ? 'inactive' : 'active').'">
<span class="up"></span>
<span class="down"></span>
</div>
<div class="text">'.$this->suggestion.'</div>
<div class="rating">'.(int)$this->rating.'</div>
</li>';
}
}
Метод __toString() используется для создания представления объекта в виде строки. Таким образом мы можем построить разметку HTML, включая заголовок предложения и количество голосов.
Метод __get() используется для предоставления доступа к неопределённым свойствам класса для массива $data. Это означает, что если мы хотим получить доступ к $obj->suggestion, а данное свойство не определено, то оно будет получено из массива $data и возвращено так, как будто оно существует. Таким образом мы можем передать массив конструктору, вместо того, чтобы устанавливать все свойства. Мы используем такой подход, когда создаем объект в ajax.php.
Теперь перейдем к генерированию неупорядоченного списка для страницы.
demo.php
require "connect.php";
require "suggestion.class.php";
// Конвертируем IP в число. Это более эффективный способ хранения IP
// в базе данных:
$ip = sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
// Следующий запрос использует left join для выбора
// всех предложений и одновременно определяет,
// голосовал ли пользователь за них.
$result = $mysqli->query("
SELECT s.*, if (v.ip IS NULL,0,1) AS have_voted
FROM suggestions AS s
LEFT JOIN suggestions_votes AS v
ON(
s.id = v.suggestion_id
AND v.day = CURRENT_DATE
AND v.ip = $ip
)
ORDER BY s.rating DESC, s.id DESC
");
$str = '';
if(!$mysqli->error)
{
// Генерируем UL
$str = '<ul class="suggestions">';
// Используем метод MySQL fetch_object для создания нового объекта
// и наполнения его столбцами результата запроса:
while($suggestion = $result->fetch_object('Suggestion')){9
$str.= $suggestion; // Используем метод __toString().
}
$str .='</ul>';
}
После выполнения запроса мы используем метод fetch_object() объекта $result. Данный метод создает объект заданного класса для каждой строки результата запроса, и назначает столбцы каждой строчки как публичные свойства объекта.
PHP также управляет запросами AJAX, отправляемыми jQuery. Это реализовано в ajax.php. Для разделения действий AJAX, скрипт использует параметр $_GET['action'], который может иметь одно из двух значений – ‘vote‘ или ‘submit‘.
ajax.php
require "connect.php";
require "suggestion.class.php";
// если запрос пришел не от AJAX, то выходим:
if($_SERVER['HTTP_X_REQUESTED_WITH'] !='XMLHttpRequest'){
exit;
}
// Конвертируем IP в число. Это более эффективный способ хранения IP в базе данных:
$ip = sprintf('%u',ip2long($_SERVER['REMOTE_ADDR']));
if($_GET['action'] == 'vote'){
$v = (int)$_GET['vote'];
$id = (int)$_GET['id'];
if($v != -1 && $v != 1){
exit;
}
// Проверяем, существует ли такой id предложения:
if(!$mysqli->query("SELECT 1 FROM suggestions WHERE id = $id")->num_rows){
exit;
}
// Поля id, ip и day является основным ключем.
// Запрос потерпит неудачу, если мы будем пытаться вставить дупликат ключа,
// что означает возможность для пользователя голосовать только один раз в день.
$mysqli->query("
INSERT INTO suggestions_votes (suggestion_id,ip,day,vote)
VALUES (
$id,
$ip,
CURRENT_DATE,
$v
)
");
if($mysqli->affected_rows == 1)
{
$mysqli->query("
UPDATE suggestions SET
".($v == 1 ? 'votes_up = votes_up + 1' : 'votes_down = votes_down + 1').",
rating = rating + $v
WHERE id = $id
");
}
}
else if($_GET['action'] == 'submit'){
if(get_magic_quotes_gpc()){
array_walk_recursive($_GET,create_function('&$v,$k','$v = stripslashes($v);'));
}
// Зачищаем контент
$_GET['content'] = htmlspecialchars(strip_tags($_GET['content']));
if(mb_strlen($_GET['content'],'utf-8')<3){
exit;
}
$mysqli->query("INSERT INTO suggestions SET suggestion = '".$mysqli->real_escape_string($_GET['content'])."'");
// Вывод HTML кода нового созданного предложения в формате JSON.
// Мы используем (string) для вызова магического метода __toString().
echo json_encode(array(
'html' => (string)(new Suggestion(array(
'id' => $mysqli->insert_id,
'suggestion' => $_GET['content']
)))
));
}
Когда jQuery отправляет запрос ‘vote‘, не предполагается никакого возвращаемого значения, таким образом скрипт ничего не выводит. При запросе ‘submit‘, jQuery ожидает возвращения объекта JSON, который содержит разметку HTML для предложения, которое только-что было вставлено. Именно здесь создается новый объект Suggestion с использованием магического метода __toString() и конвертированием с помощью функции json_encode().
Новое предложение.
Новое предложение.
jQuery
Весь код jQuery располагается в файле script.js. Он отслеживает события нажатия кнопки мыши для красной и зеленой стрелочек. Но так как предложение может быть вставлено в любой момент, то используется метод jQuery live().
script.js
$(document).ready(function(){
var ul = $('ul.suggestions');
// Отслеживаем нажатие на стрелочках ВВЕРХ или ВНИЗ:
$('div.vote span').live('click',function(){
var elem = $(this),
parent = elem.parent(),
li = elem.closest('li'),
ratingDiv = li.find('.rating'),
id = li.attr('id').replace('s',''),
v = 1;
// Если пользователь уже голосовал:
if(parent.hasClass('inactive')){
return false;
}
parent.removeClass('active').addClass('inactive');
if(elem.hasClass('down')){
v = -1;
}
// Увеличиваем счетчик справа:
ratingDiv.text(v + +ratingDiv.text());
// Помещаем все элементы LI в массив
// и сортируем его по количеству голосов:
var arr = $.makeArray(ul.find('li')).sort(function(l,r){
return +$('.rating',r).text() - +$('.rating',l).text();
});
// Добавляем отсортированные элементы LI в UL
ul.html(arr);
// Отправляем запрос AJAX
$.get('ajax.php',{action:'vote',vote:v,'id':id});
});
$('#suggest').submit(function(){
var form = $(this),
textField = $('#suggestionText');
// Предотвращение дублирования запросов:
if(form.hasClass('working') || textField.val().length<3){
return false;
}
form.addClass('working');
$.getJSON('ajax.php',{action:'submit',content:textField.val()},function(msg){
textField.val('');
form.removeClass('working');
if(msg.html){
// Добавление разметки нового созданного элемента LI на страницу:
$(msg.html).hide().appendTo(ul).slideDown();
}
});
return false;
});
});
Когда происходит событие нажатия кнопки мыши на одной из стрелочек, jQuery определяет, установлен ли класс ‘inactive’ на элементе LI. Данный класс применяется только для предложений, если пользователь уже голосовал за них в течении последнего дня. В случае наличия данного класса на элементе LI событие нажатия кнопки мыши игнорируется.
Обратите внимание, как используется $.makeArray для перевода объектов jQuery, которые содержат элементы LI, в массив. Таким образом мы можем использовать метод сортировки array.sort() и передать ему пользовательскую функцию, которая получает два элемента LI и выводит отрицательное значение, нуль или положительное значение в зависимости от того, относятся друг к другу сравниваемые элементы. Данный массив затем вставляется в неупорядоченный список.
CSS
Теперь у нас есть вся разметка и мы можем приступить к заданию стилей. В целом стили довольно примитивны, стоит только обратить внимание на закругление верхнего левого и нижнего правого углов всех элементов. Весь файл стилей CSS styles.css доступено в исходниках к уроку.
styles.css
.rounded,
#suggest,
.suggestions li{
-moz-border-radius-topleft:12px;
-moz-border-radius-bottomright:12px;
-webkit-border-top-left-radius:12px;
-webkit-border-bottom-right-radius:12px;
border-top-left-radius:12px;
border-bottom-right-radius:12px;
}
Готово!
Заключение
Если вы планируете использовать данный скрипт на вашем сервере, то вам нужно будет создать две таблицы с помощью кода SQL запросов в файле tables.sql, который можно ввести на закладке SQL в phpMyAdmin. Также нужно внести изменения в параметры соединения с базой данных в файле connect.php.
Вы можете использовать скрипт для сбора предложений от ваших пользователей. Также можно отключить опцию добавления новых предложений и получить симпатичную систему голосования.