Ajax Russia : Аякс по-русски

Свежие новости из мира IT

AJAX PHP поиск - часть 1: XML

ajax xml searchКак создавать простые AJAX запросы к серверному скрипту (PHP) вы уже научились, методами простого XML, а так же с использованием библиотеки prototype. Рассмотрим более комплексный пример - как организовать поиск по базе данных MySQL средствами AJAX и PHP.

В первой части нашего урока, мы рассмотрим как всё это сделать типичным выводом XML данных через PHP скрипт, а во второй части я попытаюсь привести аналогичный пример с использованием библиотеки prototype, а так же подвести небольшую сравнительную характеристику.

Сразу прошу обратить внимание, что статья написано о том, КАК организовать AJAX поиск, а не о том, как правильно остерегаться SQL инъекции, как лучше выводить поисковую информацию и о том, что такое индексация таблиц для полнотекстового поиска. Мы здесь изучим основы составления запроса, а методы поиска, защита, красочный и организованный вывод остаются за вами.

Наглядный пример

Чтобы вы были в курсе, что мы тут попытаемся написать, прошу взглянуть: http://logicerror.pp.ru/upload/ajax_search_xml/
Вы вводите ключевую фразу в текстовое поле, жмёте на кнопку поиска, и вуала, результаты поиска без перезагрузки страницы!

Как это работает? Всё очень просто. При нажатии на кнопку поиска, форма никуда не ведёт, а просто вызывает javascript функцию, которая, прочитав ваш поисковой запрос, отсылает его серверному скрипту (php). Этот скрипт, предварительно обработав ваш запрос, ищет совпадения в базе данных, и выводит результаты соответственно в XML формате. Получив данные, javascript отображает их в соответствующем формате.

То есть у нас имеется:

  1. база данных MySQL, где хранятся все данные
  2. файл HTML (index.html) - форма, и поле для вывода результатов
  3. файл JS (script.js) - ajax-driven скрипт - исполнитель запросов (пускай будет первого уровня), организатор вывода результатов
  4. файл PHP (search.php) - исполнитель запросов (второго уровня), и вывод результатов в виде XML для дальнейшей обработки

Рассмотрим по порядку.

Источник данных

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

SQL:
  1. CREATE TABLE `articles` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT,
  3.   `title` varchar(255) collate utf8_unicode_ci NOT NULL,
  4.   `content` text collate utf8_unicode_ci NOT NULL,
  5.   `author` varchar(255) collate utf8_unicode_ci NOT NULL,
  6.   `timestamp` int(11) NOT NULL,
  7.   UNIQUE KEY `id` (`id`)
  8. ) 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 – страница вывода

CODE:
  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Language" content="ru">
  5. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  6. <title>AJAX PHP search example - XML</title>
  7.  
  8. <link rel="stylesheet" href="style.css" type="text/css">
  9. <script src="script.js" type="text/javascript"></script>
  10.  
  11. </head>
  12. <body>
  13. <div id="wrap">
  14. <form onsubmit="search(); return false;">
  15. <input type="text" class="input" id="search_input" value=""> <input type="submit" class="button" id="search_button" value="&raquo; поиск">
  16. </form><br>
  17.  
  18. <div id="search_results">
  19. </div>
  20.  
  21. <div id="searching">
  22.     searching
  23. </div>
  24.  
  25. </div>
  26. </body>
  27. </html>

Проблем здесь возникнуть тоже не должно, поскольку вы прекрасно (надеюсь) знаете, что такое id и чем отличается от class.

Объясню некоторые важные моменты:

  • search_input – текстовое поле для ввода строки поиска
  • search_results – поле для вывода результатов
  • searching – надпись «подождите, идет поиск…»

Ну а как вы, наверное, уже догадались, при отправки формы (onsubmit) мы вызываем функцию search(); и возвращаем false (думаю, никто уже не посмеет спросить зачем).

CSS в комплекте: http://logicerror.pp.ru/upload/ajax_search_xml/style.css

AJAX – посредник информации

Давайте сперва взглянем на скрипт целиком, а затем рассмотрим по частям.

JavaScript:
  1. function getXmlHttp() {
  2.   var xmlhttp;
  3.   try {
  4.     xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  5.   } catch (e) {
  6.       try {
  7.         xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  8.       } catch (E) {
  9.       xmlhttp = false;
  10.     }
  11.   }
  12.  
  13.   if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
  14.     xmlhttp = new XMLHttpRequest();
  15.   }
  16.   return xmlhttp;
  17. }
  18.  
  19. function search() {
  20.   var sSearch = document.getElementById("search_input").value;
  21.  
  22.   if (sSearch.length <3)
  23.   {
  24.     alert("Запрос должен быть не короче 3-х символов.");
  25.     return false;
  26.   }
  27.  
  28.   var xmlHttp;
  29.   xmlHttp = getXmlHttp();
  30.  
  31.   var obj = document.getElementById("search_results");
  32.   obj.innerHTML = "";
  33.   var loading = document.getElementById("searching");
  34.   loading.style.display = "block";
  35.  
  36.   xmlHttp.onreadystatechange = function()  {
  37.     if (xmlHttp.readyState == 4)
  38.     {
  39.       loading.style.display = "none";
  40.       var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("entry");
  41.       for (i = xmlDoc.length-1; i>= 0 ; i--)
  42.       {
  43.         var new_el = document.createElement("div");
  44.         var sTitle = xmlDoc[i].getElementsByTagName("title")[0].firstChild.nodeValue;
  45.         var sContent = xmlDoc[i].getElementsByTagName("content")[0].firstChild.nodeValue;
  46.         
  47.         new_el.innerHTML = "<h1>"+sTitle+"</h1>"+sContent;
  48.         new_el.className = "result";
  49.         
  50.         obj.appendChild(new_el);
  51.       }
  52.     }
  53.   }
  54.  
  55.   xmlHttp.open('GET', 'search.php?search='+sSearch1111, true);
  56.   xmlHttp.send(null);
  57. }

Функцию getXmlHttp() мы уже с вами разбирали, а вот на функции search() остановимся по подробнее.

Сразу забегу чуток вперед, так как нужно знать в каком формате нам приходят XML данные, перед тем как их обрабатывать. Кусок результатов запроса:

XML:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <searchresults>
  3. <entry>
  4.     <title>Мобильный путеводитель по итогам прошедшей недели</title>
  5.     <content>Мobime снова тепло встречает вас на на своей еженедельной новостной страничке. Просьба не толпиться, а тихонько занять места у своих мониторов – событий прошедшей недели хватит всем. Сегодня вы узнаете новые подробности о готовящемся коммуникаторе XP...</content>
  6. </entry>
  7. <entry>
  8.     <title>Обзор Sony Ericsson K850i – часть первая</title>
  9.     <content>Обычно во вступлении к таким знаковым продуктам упоминается предыстория создания, взгляды поклонников компании на примерный функциональный набор продукта, специфику анонсирования и собственно сам выход на прилавки магазинов. Ничего этого мы сегодня р...</content>
  10. </entry>
  11. </searchresults>

Корневое поле здесь у нас searchresults. От него следует ветка entry – запись. В записи мы имеем title – заголовок статьи, и content – небольшая часть самой статьи. Вернемся к нашему AJAX скрипту и пройдемся по порядку.

JavaScript:
  1. var sSearch = document.getElementById("search_input").value;
  2.  
  3. if (sSearch.length <3)
  4. {
  5.   alert("Запрос должен быть не короче 3-х символов.");
  6.   return false;
  7. }

Здесь мы в переменную sSearch помещаем запрос, введенный в текстовое поле с идентификатором search_input, а так же проверяем его длину на значение больше 3-х (глупо искать по 3-м символам…).

JavaScript:
  1. var xmlHttp;
  2. xmlHttp = getXmlHttp();

Создали объект xmlHttp с помощью функции getXmlHttp().

JavaScript:
  1. var obj = document.getElementById("search_results");
  2. obj.innerHTML = "";
  3. var loading = document.getElementById("searching");
  4. loading.style.display = "block";

Очищаем предыдущие результаты поиска, при их наличии (блок search_results), а так же выводим строку «ждите, ищу» (помните?). Объект obj нам пригодится в дальнейшем для размещения новых результатов поиска.

JavaScript:
  1. xmlHttp.onreadystatechange = function()  {
  2.   if (xmlHttp.readyState == 4)
  3.   {

Здесь, уже известный нам, обработчик событий («слушатель»), и при состоянии объекта xmlHttp равное 4 (готов) начинаем обработку данных.

JavaScript:
  1. loading.style.display = "none";
  2. var xmlDoc = xmlHttp.responseXML.documentElement.getElementsByTagName("entry");

Для начала можно спрятать надпись «ждите». Затем, создаем новый объект xmlDoc и записываем в него весь массив XML данных имеющих отношение к ветке entry (т.е. те данные, которые лежат внутри этой ветки (title, content)).

JavaScript:
  1. for (i = xmlDoc.length-1; i>= 0 ; i--)
  2. {
  3.   var new_el = document.createElement("div");

Здесь мы в цикле прогоняем каждый элемент массива и создаем для него новый элемент div. Но, перед тем, как прикрепить (приклеить) новый элемент в поле результатов, нужно его наполнить данными:

JavaScript:
  1. var sTitle = xmlDoc[i].getElementsByTagName("title")[0].firstChild.nodeValue;
  2. var sContent = xmlDoc[i].getElementsByTagName("content")[0].firstChild.nodeValue;
  3.  
  4. new_el.innerHTML = "<h1>"+sTitle+"</h1>"+sContent;
  5. new_el.className = "result";

В переменные sTitle и sContent записываем соответствующие данных из XML массива (title, content). Этот метод мы уже рассматривали в предыдущих уроках (ссылки на предыдущие уроки). Следом, записываем эти значения в наш новый элемент (для заголовка статьи используется тэг h1).

JavaScript:
  1. obj.appendChild(new_el);

Ну и здесь мы, наконец, приклеиваем наш новый элемент в поле результатов поиска (см. выше obj search_results). Обработчик событий готов, закрываем все операторные скобки и нам остается написать сам запрос.

JavaScript:
  1. xmlHttp.open('GET', 'search.php?search='+sSearch+'&rand='+Math.random(), true);
  2. xmlHttp.send(null);

Простой AJAX GET запрос серверному скрипту search.php с передачей параметров sSearch - строка поиска, и случайно число во избежание кэширование на некоторых браузерах.

PHP – server-side coding

PHP:
  1. <?php
  2.   $db = mysql_connect("localhost", "root", "");
  3.   mysql_select_db("ajax_search");
  4.   mysql_query("SET CHARACTER SET utf8");
  5.  
  6.   header('Content-type: application/xml; charset=utf-8');
  7.   header('Cache-Control: no-cache');
  8.  
  9.   echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
  10.   echo "<searchresults>\n";
  11.  
  12.   $sString = mysql_real_escape_string($_GET["search"], $db);
  13.   $sql="SELECT * FROM `articles` WHERE `title` LIKE '%$sString%' OR `content` LIKE '%$sString%' ORDER BY `id` DESC LIMIT 10";
  14.   $rs=mysql_query($sql,$db);
  15.  
  16.   if (mysql_num_rows($rs)> 0)
  17.   {
  18.     while ($row = mysql_fetch_array($rs))
  19.     {
  20.       $content=htmlspecialchars(strip_tags($row["content"]));
  21.       if (mb_strlen($content, "utf-8")> 250) $content = mb_substr($content, 0, 250, "utf-8") . "...";
  22. ?>
  23. <entry>
  24.   <title><?=htmlspecialchars($row["title"]);?></title>
  25.   <content><?=$content;?></content>
  26. </entry>
  27. <?
  28.     }
  29.   }
  30.   else
  31.   {
  32. ?>
  33. <entry>
  34.   <title> </title>
  35.   <content>Ничего не найдено</content>
  36. </entry>
  37. <?
  38.   }
  39. ?>
  40. </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).

Разберем по порядку.

PHP:
  1. $db = mysql_connect("localhost", "root", "");
  2. mysql_select_db("ajax_search");
  3. mysql_query("SET CHARACTER SET utf8");
  4.  
  5. header('Content-type: application/xml; charset=utf-8');
  6. header('Cache-Control: no-cache');
  7.  
  8. echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
  9. echo "<searchresults>\n";

Итак, подключаемся к серверу mysql, выбираем базу данных, указываем кодировку. Далее пару заголовков о том, что это за файл и как кэшировать. Затем стандартный XML заголовок и корневой элемент searchresults.

PHP:
  1. $sString = mysql_real_escape_string($_GET["search"], $db);
  2. $sql="SELECT * FROM `articles` WHERE `title` LIKE '%$sString%' OR `content` LIKE '%$sString%' ORDER BY `id` DESC LIMIT 10";
  3. $rs=mysql_query($sql,$db);

Строку поиска берем из HTTP GET заголовка (именно так нам передаёт ее ajax скрипт), причем обработав ее функцией mysql_real_escape_string(), и пишем запрос на поиск по базе. Кто не понял, как построен запрос в базу, советую подучить SQL.

PHP:
  1. $content=htmlspecialchars(strip_tags($row["content"]));
  2. if (mb_strlen($content, "utf-8")> 250) $content = mb_substr($content, 0, 250, "utf-8") . "...";

Выбрав интересующие нас данные из базы, мы обрабатываем поле content, так как оно может быть достаточно длинным, а в результатах поиска не следует выводить статью целиком. Обрезаем ровно 250 символов и добавляем троеточие.

PHP:
  1. <entry>
  2.   <title><?=htmlspecialchars($row["title"]);?></title>
  3.   <content><?=$content;?></content>
  4. </entry>

Ну а дальше выводим данные в соответствующем формате. Элемент entry (запись), внутри элементы title и content (заголовок и контент).

PHP:
  1. <entry>
  2.   <title> </title>
  3.   <content>Ничего не найдено</content>
  4. </entry>

Здесь, в случае возврата 0 результатов, мы пишем точно такую же запись (т.е. в таком же формате), с пустым заголовком (используется пробел, так как javascript не очень любит пустые поля) и надписью «ничего не найдено».

Не забудьте закрыть корневой элемент searchresults.

И всё?

Нет, это далеко не всё, и пускать такое прямо в сеть еще рано. Не забывайте про безопасность в первую очередь. Ну и поработайте над внешним видом - например сделать это как мини-элемент на странице, небольшое окошко поиска. Не забудьте на результаты ссылочки добавить ;)

Полезные ссылки

del.icio.us Забобрить!

14 Comments so far

  1. Samolisov Pavel April 23rd, 2008 08:19

    Интересная статья не только для новичков. Но меня интересует такой вопрос: а зачем XML? Я считаю как транспорт гораздо удобнее JSON - во первых JSON это натив для ява-скрипта - его не нужно парсить в отличие от XML, делаешь eval и все, переданные поля доступны через точку. ИМХО будет выигрыш по скорости написания скрипта и по скорости выполнения.

  2. Константин April 23rd, 2008 08:45

    Павел, мы это уже обсуждали в предыдущих статьях. Еще проще будет сделать это с использованием библиотеки prototype. А как это сделать я покажу в следующем уроке буквально на днях.

    Сам лично я не против XML и не против JSON, но склоняюсь больше к XML, т.к. он мне ближе, да и сам по себе имхо по “стандартнее” так сказать. Ну например можно хранить одни и те же данные как для какого-либо ajax запроса, так и для подписки на RSS ленту.

  3. Samolisov Pavel April 23rd, 2008 08:56

    >> Ну например можно хранить одни и те же данные как для какого-либо ajax запроса, так и для подписки на RSS ленту.

    Да я не говорю что XML плохо - каждому овощу свой срок как говорится. По поводу хранить данные - я считаю что хранить данные все же надо в БД, а оттуда уже их доставать и преобразовывать в нужный формат (например нужным сервлетом, если говорить в терминах явы). Тут кстати возникает вопрос разделения логики и представления, потому что в общем случае данные могут не просто хранится в базе - а их еще надо получить, обработать и потом только сформировать фид или другое какое представление.

    З.Ы. на самом деле - повторюсь, серверу как правило все равно что обрабатывать. И для XML и для JSON написана куча библиотек и под PHP и под Java (да, я знаю что PHP начал поддерживать JSON только с 5.2 но начал же). А вот клиенту - совсем не все равно. Если будете писать статью по использованию JSON в качестве транспорта - проведите сравнение. Думаю не одному мне будет интересно.

  4. Константин April 23rd, 2008 10:03

    Полностью с вами согласен. Хорошо. Кажись продолжение будет до самой 3-й части ;)

    P.S. Когда я говорил о хранении данных я и имел ввиду их представление :)

  5. Артём Курапов April 23rd, 2008 10:20

    В общем всё правильно (для начинающих). А если в детали вдаваться, то надо добавлять или менять на JSON (учите генирить в php), Sphinx (в нагруженных базах), многоязычность, темплейты или хотя-бы избавится от этого ужасного прерывания php-тэгов.

  6. Samolisov Pavel April 23rd, 2008 11:00

    Артем, я считаю вы не правы насчет шаблонов и тд. Т.е. конечно надо делать с шаблонами, используя Smarty например, но это уведет от главного - понимания как сделать что-то полезное, используя аякс… За деревьями можно будет не заметить леса.

  7. Константин April 23rd, 2008 13:35

    Артём, согласен, есть доля правды. Видите, дело в том, что урок показывает как работает AJAX - Asynchronous JavaScript and XML. А не php multilanguage templating, sphinx и как правильнее “генерить” JSON в php. Об этом вы можете почитать в других тематических статьях ;)

    JSON не в счёт, его мы обсуждали чуть выше, да и в предыдущих уроках…

  8. larin April 24th, 2008 10:03

    @Артём Курапов
    Полностью с вами согласен. Добавить в принципе нечего, просто хотел выразить согласие )))

    @Samolisov Pavel
    На счет леса. Нормальный программист все заметит. А о “программистах” которые способны “заблудиться с трех соснах” беспокоиться не стоит.

  9. Гвидон Маляров April 24th, 2008 14:47

    Присоединяюсь к предложениям по использованию JSON…как-то это было бы…”модней” что ли:) (не говоря уже о пресловутой нативности). К тому же, по моему мнению поиск как правило актуален в тех случаях где имеет место большой объем просматриваемых данных, а там уже и скорость критична. За сим сдается мне, что ответ в формате JSON быстрей дойдет до глаз пользователя:)

  10. Константин April 25th, 2008 11:55

    Гвидон Маляров, согласен, об этом в “up-coming” уроке “Введение в JSON”. Мы будем разбирать эту же задачу, используя JSON для представления и передачи данных.

  11. Михаил April 26th, 2008 12:23

    ммм дамс
    есть над чем подумать

  12. gudea September 15th, 2008 13:49

    У меня проблемы с кодировкой. При вводе русского текста, результат не выводиться…

  13. Константин September 15th, 2008 18:10

    Попробуйте работать с utf-8, всё получится.

Leave a reply