2 Авг
Как создавать простые AJAX запросы к серверному скрипту (PHP) вы уже научились, методами простого XML, а так же с использованием библиотеки prototype. Рассмотрим более комплексный пример - как организовать поиск по базе данных MySQL средствами AJAX и PHP.
В первой части нашего урока, мы рассмотрим как всё это сделать типичным выводом XML данных через PHP скрипт, а во второй части я попытаюсь привести аналогичный пример с использованием библиотеки prototype, а так же подвести небольшую сравнительную характеристику.
Сразу прошу обратить внимание, что статья написано о том, КАК организовать AJAX поиск, а не о том, как правильно остерегаться SQL инъекции, как лучше выводить поисковую информацию и о том, что такое индексация таблиц для полнотекстового поиска. Мы здесь изучим основы составления запроса, а методы поиска, защита, красочный и организованный вывод остаются за вами.
Чтобы вы были в курсе, что мы тут попытаемся написать, прошу взглянуть: http://logicerror.pp.ru/upload/ajax_search_xml/
Вы вводите ключевую фразу в текстовое поле, жмёте на кнопку поиска, и вуала, результаты поиска без перезагрузки страницы!
Как это работает? Всё очень просто. При нажатии на кнопку поиска, форма никуда не ведёт, а просто вызывает javascript функцию, которая, прочитав ваш поисковой запрос, отсылает его серверному скрипту (php). Этот скрипт, предварительно обработав ваш запрос, ищет совпадения в базе данных, и выводит результаты соответственно в XML формате. Получив данные, javascript отображает их в соответствующем формате.
То есть у нас имеется:
Рассмотрим по порядку.
Здесь говорить особо не о чем. В общем, пускай у нас будет небольшой сборник статей. С базами данных все уже работали, так что сразу к структуре:
CREATE TABLE `articles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) collate utf8_unicode_ci NOT NULL, `content` text collate utf8_unicode_ci NOT NULL, `author` varchar(255) collate utf8_unicode_ci NOT NULL, `timestamp` int(11) NOT NULL, UNIQUE KEY `id` (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;
Работать мы будем всего с двумя полями, остальные берем как запас – вдруг пригодятся. Поле title – заголовок статьи, и поле content – собственно статья.
Далее, наполняете свою таблицу мусором (если кому в голову мусор не приходит, можно взять мусор здесь: http://logicerror.pp.ru/upload/ajax_search_xml/junk.sql).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <meta http-equiv="Content-Language" content="ru"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>AJAX PHP search example - XML</title> <link rel="stylesheet" href="style.css" type="text/css"> <script src="script.js" type="text/javascript"></script> </head> <body> <div id="wrap"> <form onsubmit="search(); return false;"> <input type="text" class="input" id="search_input" value=""> <input type="submit" class="button" id="search_button" value="» поиск"> </form><br> <div id="search_results"> </div> <div id="searching"> searching </div> </div> </body> </html> |
Проблем здесь возникнуть тоже не должно, поскольку вы прекрасно (надеюсь) знаете, что такое id и чем отличается от class.
Объясню некоторые важные моменты:
Ну а как вы, наверное, уже догадались, при отправки формы (onsubmit) мы вызываем функцию search(); и возвращаем false (думаю, никто уже не посмеет спросить зачем).
CSS в комплекте: http://logicerror.pp.ru/upload/ajax_search_xml/style.css
Давайте сперва взглянем на скрипт целиком, а затем рассмотрим по частям.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | function getXmlHttp() { var xmlhttp; try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (E) { xmlhttp = false; } } if (!xmlhttp && typeof XMLHttpRequest != 'undefined') { xmlhttp = new XMLHttpRequest(); } return xmlhttp; } function search() { var sSearch = document.getElementById("search_input").value; if (sSearch.length < 3) { alert("Запрос должен быть не короче 3-х символов."); return false; } var xmlHttp; xmlHttp = getXmlHttp(); var obj = document.getElementById("search_results"); obj.innerHTML = ""; var loading = document.getElementById("searching"); loading.style.display = "block"; xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { loading.style.display = "none"; var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("entry"); for (i = xmlDoc.length-1; i>= 0 ; i--) { var new_el = document.createElement("div"); var sTitle = xmlDoc[i].getElementsByTagName("title")[0].firstChild.nodeValue; var sContent = xmlDoc[i].getElementsByTagName("content")[0].firstChild.nodeValue; new_el.innerHTML = "<h1>"+sTitle+"</h1>"+sContent; new_el.className = "result"; obj.appendChild(new_el); } } } xmlHttp.open('GET', 'search.php?search='+sSearch, true); xmlHttp.send(null); } |
Функцию getXmlHttp() мы уже с вами разбирали, а вот на функции search() остановимся по подробнее.
Сразу забегу чуток вперед, так как нужно знать в каком формате нам приходят XML данные, перед тем как их обрабатывать. Кусок результатов запроса:
1 2 3 4 5 6 7 8 9 10 11 | <?xml version="1.0" encoding="utf-8"?> <searchresults> <entry> <title>Мобильный путеводитель по итогам прошедшей недели</title> <content>Мobime снова тепло встречает вас на на своей еженедельной новостной страничке. Просьба не толпиться, а тихонько занять места у своих мониторов – событий прошедшей недели хватит всем. Сегодня вы узнаете новые подробности о готовящемся коммуникаторе XP...</content> </entry> <entry> <title>Обзор Sony Ericsson K850i – часть первая</title> <content>Обычно во вступлении к таким знаковым продуктам упоминается предыстория создания, взгляды поклонников компании на примерный функциональный набор продукта, специфику анонсирования и собственно сам выход на прилавки магазинов. Ничего этого мы сегодня р...</content> </entry> </searchresults> |
Корневое поле здесь у нас searchresults. От него следует ветка entry – запись. В записи мы имеем title – заголовок статьи, и content – небольшая часть самой статьи. Вернемся к нашему AJAX скрипту и пройдемся по порядку.
var sSearch = document.getElementById("search_input").value; if (sSearch.length < 3) { alert("Запрос должен быть не короче 3-х символов."); return false; }
Здесь мы в переменную sSearch помещаем запрос, введенный в текстовое поле с идентификатором search_input, а так же проверяем его длину на значение больше 3-х (глупо искать по 3-м символам…).
var xmlHttp; xmlHttp = getXmlHttp();
Создали объект xmlHttp с помощью функции getXmlHttp().
var obj = document.getElementById("search_results"); obj.innerHTML = ""; var loading = document.getElementById("searching"); loading.style.display = "block";
Очищаем предыдущие результаты поиска, при их наличии (блок search_results), а так же выводим строку «ждите, ищу» (помните?). Объект obj нам пригодится в дальнейшем для размещения новых результатов поиска.
xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) {
Здесь, уже известный нам, обработчик событий («слушатель»), и при состоянии объекта xmlHttp равное 4 (готов) начинаем обработку данных.
loading.style.display = "none"; var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("entry");
Для начала можно спрятать надпись «ждите». Затем, создаем новый объект xmlDoc и записываем в него весь массив XML данных имеющих отношение к ветке entry (т.е. те данные, которые лежат внутри этой ветки (title, content)).
for (i = xmlDoc.length-1; i>= 0 ; i--) { var new_el = document.createElement("div");
Здесь мы в цикле прогоняем каждый элемент массива и создаем для него новый элемент div. Но, перед тем, как прикрепить (приклеить) новый элемент в поле результатов, нужно его наполнить данными:
var sTitle = xmlDoc[i].getElementsByTagName("title")[0].firstChild.nodeValue; var sContent = xmlDoc[i].getElementsByTagName("content")[0].firstChild.nodeValue; new_el.innerHTML = "<h1>"+sTitle+"</h1>"+sContent; new_el.className = "result";
В переменные sTitle и sContent записываем соответствующие данных из XML массива (title, content). Этот метод мы уже рассматривали в предыдущих уроках (ссылки на предыдущие уроки). Следом, записываем эти значения в наш новый элемент (для заголовка статьи используется тэг h1).
obj.appendChild(new_el);
Ну и здесь мы, наконец, приклеиваем наш новый элемент в поле результатов поиска (см. выше obj search_results). Обработчик событий готов, закрываем все операторные скобки и нам остается написать сам запрос.
xmlHttp.open('GET', 'search.php?search='+sSearch+'&rand='+Math.random(), true); xmlHttp.send(null);
Простой AJAX GET запрос серверному скрипту search.php с передачей параметров sSearch - строка поиска, и случайно число во избежание кэширование на некоторых браузерах.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <?php $db = mysql_connect("localhost", "root", ""); mysql_select_db("ajax_search"); mysql_query("SET CHARACTER SET utf8"); header('Content-type: application/xml; charset=utf-8'); header('Cache-Control: no-cache'); echo '<?xml version="1.0" encoding="utf-8"?>' . "\n"; echo "<searchresults>\n"; $sString = mysql_real_escape_string($_GET["search"], $db); $sql="SELECT * FROM `articles` WHERE `title` LIKE '%$sString%' OR `content` LIKE '%$sString%' ORDER BY `id` DESC LIMIT 10"; $rs=mysql_query($sql,$db); if (mysql_num_rows($rs) > 0) { while ($row = mysql_fetch_array($rs)) { $content=htmlspecialchars(strip_tags($row["content"])); if (mb_strlen($content, "utf-8") > 250) $content = mb_substr($content, 0, 250, "utf-8") . "..."; ?> <entry> <title><?=htmlspecialchars($row["title"]);?></title> <content><?=$content;?></content> </entry> <? } } else { ?> <entry> <title> </title> <content>Ничего не найдено</content> </entry> <? } ?> </searchresults> |
Ну и наконец-то мы добрались до нашего главного дяди. Объяснять здесь тоже особо нечего. Прошу обратить внимание на одно. В предыдущих уроках мы использовали кодировку windows-1251 (cp1251, сравнение cp1251_general_ci), и мучались с переводом данных из utf8 в cp1251. Данный урок построен полностью на кодировке UTF-8, однако здесь возникла проблема с функциями strlen() и substr(). Для получения подстроки из кодировки UTF-8 следует использовать функцию mb_substr(), причем передав ей, 4-м параметром, строку «utf-8» (http://php.net/mb_substr). Функция mb_substr() работает аналогично (http://php.net/mb_strlen).
Разберем по порядку.
1 2 3 4 5 6 7 8 9 | $db = mysql_connect("localhost", "root", "");
mysql_select_db("ajax_search");
mysql_query("SET CHARACTER SET utf8");
header('Content-type: application/xml; charset=utf-8');
header('Cache-Control: no-cache');
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
echo "<searchresults>\n"; |
Итак, подключаемся к серверу mysql, выбираем базу данных, указываем кодировку. Далее пару заголовков о том, что это за файл и как кэшировать. Затем стандартный XML заголовок и корневой элемент searchresults.
$sString = mysql_real_escape_string($_GET["search"], $db); $sql="SELECT * FROM `articles` WHERE `title` LIKE '%$sString%' OR `content` LIKE '%$sString%' ORDER BY `id` DESC LIMIT 10"; $rs=mysql_query($sql,$db);
Строку поиска берем из HTTP GET заголовка (именно так нам передаёт ее ajax скрипт), причем обработав ее функцией mysql_real_escape_string(), и пишем запрос на поиск по базе. Кто не понял, как построен запрос в базу, советую подучить SQL.
$content=htmlspecialchars(strip_tags($row["content"])); if (mb_strlen($content, "utf-8") > 250) $content = mb_substr($content, 0, 250, "utf-8") . "...";
Выбрав интересующие нас данные из базы, мы обрабатываем поле content, так как оно может быть достаточно длинным, а в результатах поиска не следует выводить статью целиком. Обрезаем ровно 250 символов и добавляем троеточие.
<entry> <title><?=htmlspecialchars($row["title"]);?></title> <content><?=$content;?></content> </entry>
Ну а дальше выводим данные в соответствующем формате. Элемент entry (запись), внутри элементы title и content (заголовок и контент).
<entry> <title> </title> <content>Ничего не найдено</content> </entry>
Здесь, в случае возврата 0 результатов, мы пишем точно такую же запись (т.е. в таком же формате), с пустым заголовком (используется пробел, так как javascript не очень любит пустые поля) и надписью «ничего не найдено».
Не забудьте закрыть корневой элемент searchresults.
Нет, это далеко не всё, и пускать такое прямо в сеть еще рано. Не забывайте про безопасность в первую очередь. Ну и поработайте над внешним видом - например сделать это как мини-элемент на странице, небольшое окошко поиска. Не забудьте на результаты ссылочки добавить ;)
Оставьте отзыв