AJAX PHP поиск - часть 1: XML
Как создавать простые AJAX запросы к серверному скрипту (PHP) вы уже научились, методами простого XML, а так же с использованием библиотеки prototype. Рассмотрим более комплексный пример - как организовать поиск по базе данных MySQL средствами AJAX и PHP.
В первой части нашего урока, мы рассмотрим как всё это сделать типичным выводом XML данных через PHP скрипт, а во второй части я попытаюсь привести аналогичный пример с использованием библиотеки prototype, а так же подвести небольшую сравнительную характеристику.
Сразу прошу обратить внимание, что статья написано о том, КАК организовать AJAX поиск, а не о том, как правильно остерегаться SQL инъекции, как лучше выводить поисковую информацию и о том, что такое индексация таблиц для полнотекстового поиска. Мы здесь изучим основы составления запроса, а методы поиска, защита, красочный и организованный вывод остаются за вами.
Наглядный пример
Чтобы вы были в курсе, что мы тут попытаемся написать, прошу взглянуть: http://logicerror.pp.ru/upload/ajax_search_xml/
Вы вводите ключевую фразу в текстовое поле, жмёте на кнопку поиска, и вуала, результаты поиска без перезагрузки страницы!
Как это работает? Всё очень просто. При нажатии на кнопку поиска, форма никуда не ведёт, а просто вызывает javascript функцию, которая, прочитав ваш поисковой запрос, отсылает его серверному скрипту (php). Этот скрипт, предварительно обработав ваш запрос, ищет совпадения в базе данных, и выводит результаты соответственно в XML формате. Получив данные, javascript отображает их в соответствующем формате.
То есть у нас имеется:
- база данных MySQL, где хранятся все данные
- файл HTML (index.html) - форма, и поле для вывода результатов
- файл JS (script.js) - ajax-driven скрипт - исполнитель запросов (пускай будет первого уровня), организатор вывода результатов
- файл PHP (search.php) - исполнитель запросов (второго уровня), и вывод результатов в виде XML для дальнейшей обработки
Рассмотрим по порядку.
Источник данных
Здесь говорить особо не о чем. В общем, пускай у нас будет небольшой сборник статей. С базами данных все уже работали, так что сразу к структуре:
-
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).
HTML visual – страница вывода
-
<!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.
Объясню некоторые важные моменты:
- search_input – текстовое поле для ввода строки поиска
- search_results – поле для вывода результатов
- searching – надпись «подождите, идет поиск…»
Ну а как вы, наверное, уже догадались, при отправки формы (onsubmit) мы вызываем функцию search(); и возвращаем false (думаю, никто уже не посмеет спросить зачем).
CSS в комплекте: http://logicerror.pp.ru/upload/ajax_search_xml/style.css
AJAX – посредник информации
Давайте сперва взглянем на скрипт целиком, а затем рассмотрим по частям.
-
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='+sSearch1111, true);
-
xmlHttp.send(null);
-
}
Функцию getXmlHttp() мы уже с вами разбирали, а вот на функции search() остановимся по подробнее.
Сразу забегу чуток вперед, так как нужно знать в каком формате нам приходят XML данные, перед тем как их обрабатывать. Кусок результатов запроса:
-
<?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 - строка поиска, и случайно число во избежание кэширование на некоторых браузерах.
PHP – server-side coding
-
<?php
-
-
-
echo "<searchresults>\n";
-
-
$sql="SELECT * FROM `articles` WHERE `title` LIKE '%$sString%' OR `content` LIKE '%$sString%' ORDER BY `id` DESC LIMIT 10";
-
-
{
-
{
-
?>
-
<entry>
-
<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).
Разберем по порядку.
-
-
-
echo "<searchresults>\n";
Итак, подключаемся к серверу mysql, выбираем базу данных, указываем кодировку. Далее пару заголовков о том, что это за файл и как кэшировать. Затем стандартный XML заголовок и корневой элемент searchresults.
-
$sql="SELECT * FROM `articles` WHERE `title` LIKE '%$sString%' OR `content` LIKE '%$sString%' ORDER BY `id` DESC LIMIT 10";
Строку поиска берем из HTTP GET заголовка (именно так нам передаёт ее ajax скрипт), причем обработав ее функцией mysql_real_escape_string(), и пишем запрос на поиск по базе. Кто не понял, как построен запрос в базу, советую подучить SQL.
Выбрав интересующие нас данные из базы, мы обрабатываем поле content, так как оно может быть достаточно длинным, а в результатах поиска не следует выводить статью целиком. Обрезаем ровно 250 символов и добавляем троеточие.
-
<entry>
-
<content><?=$content;?></content>
-
</entry>
Ну а дальше выводим данные в соответствующем формате. Элемент entry (запись), внутри элементы title и content (заголовок и контент).
-
<entry>
-
<title> </title>
-
<content>Ничего не найдено</content>
-
</entry>
Здесь, в случае возврата 0 результатов, мы пишем точно такую же запись (т.е. в таком же формате), с пустым заголовком (используется пробел, так как javascript не очень любит пустые поля) и надписью «ничего не найдено».
Не забудьте закрыть корневой элемент searchresults.
И всё?
Нет, это далеко не всё, и пускать такое прямо в сеть еще рано. Не забывайте про безопасность в первую очередь. Ну и поработайте над внешним видом - например сделать это как мини-элемент на странице, небольшое окошко поиска. Не забудьте на результаты ссылочки добавить
Полезные ссылки
del.icio.us Забобрить!14 Comments so far
Leave a reply
Интересная статья не только для новичков. Но меня интересует такой вопрос: а зачем XML? Я считаю как транспорт гораздо удобнее JSON - во первых JSON это натив для ява-скрипта - его не нужно парсить в отличие от XML, делаешь eval и все, переданные поля доступны через точку. ИМХО будет выигрыш по скорости написания скрипта и по скорости выполнения.
Павел, мы это уже обсуждали в предыдущих статьях. Еще проще будет сделать это с использованием библиотеки prototype. А как это сделать я покажу в следующем уроке буквально на днях.
Сам лично я не против XML и не против JSON, но склоняюсь больше к XML, т.к. он мне ближе, да и сам по себе имхо по “стандартнее” так сказать. Ну например можно хранить одни и те же данные как для какого-либо ajax запроса, так и для подписки на RSS ленту.
>> Ну например можно хранить одни и те же данные как для какого-либо ajax запроса, так и для подписки на RSS ленту.
Да я не говорю что XML плохо - каждому овощу свой срок как говорится. По поводу хранить данные - я считаю что хранить данные все же надо в БД, а оттуда уже их доставать и преобразовывать в нужный формат (например нужным сервлетом, если говорить в терминах явы). Тут кстати возникает вопрос разделения логики и представления, потому что в общем случае данные могут не просто хранится в базе - а их еще надо получить, обработать и потом только сформировать фид или другое какое представление.
З.Ы. на самом деле - повторюсь, серверу как правило все равно что обрабатывать. И для XML и для JSON написана куча библиотек и под PHP и под Java (да, я знаю что PHP начал поддерживать JSON только с 5.2 но начал же). А вот клиенту - совсем не все равно. Если будете писать статью по использованию JSON в качестве транспорта - проведите сравнение. Думаю не одному мне будет интересно.
Полностью с вами согласен. Хорошо. Кажись продолжение будет до самой 3-й части
P.S. Когда я говорил о хранении данных я и имел ввиду их представление
В общем всё правильно (для начинающих). А если в детали вдаваться, то надо добавлять или менять на JSON (учите генирить в php), Sphinx (в нагруженных базах), многоязычность, темплейты или хотя-бы избавится от этого ужасного прерывания php-тэгов.
Артем, я считаю вы не правы насчет шаблонов и тд. Т.е. конечно надо делать с шаблонами, используя Smarty например, но это уведет от главного - понимания как сделать что-то полезное, используя аякс… За деревьями можно будет не заметить леса.
Артём, согласен, есть доля правды. Видите, дело в том, что урок показывает как работает AJAX - Asynchronous JavaScript and XML. А не php multilanguage templating, sphinx и как правильнее “генерить” JSON в php. Об этом вы можете почитать в других тематических статьях
JSON не в счёт, его мы обсуждали чуть выше, да и в предыдущих уроках…
@Артём Курапов
Полностью с вами согласен. Добавить в принципе нечего, просто хотел выразить согласие )))
@Samolisov Pavel
На счет леса. Нормальный программист все заметит. А о “программистах” которые способны “заблудиться с трех соснах” беспокоиться не стоит.
Присоединяюсь к предложениям по использованию JSON…как-то это было бы…”модней” что ли:) (не говоря уже о пресловутой нативности). К тому же, по моему мнению поиск как правило актуален в тех случаях где имеет место большой объем просматриваемых данных, а там уже и скорость критична. За сим сдается мне, что ответ в формате JSON быстрей дойдет до глаз пользователя:)
Гвидон Маляров, согласен, об этом в “up-coming” уроке “Введение в JSON”. Мы будем разбирать эту же задачу, используя JSON для представления и передачи данных.
ммм дамс
есть над чем подумать
[...] http://www.ajaxrussia.com/archives/ajax-php-search-xml [...]
У меня проблемы с кодировкой. При вводе русского текста, результат не выводиться…
Попробуйте работать с utf-8, всё получится.