Ajax: прошлое, настоящее, будущее. Часть 2

Продолжаем серию статей, посвященных технологии ajax. В прошлый раз (см. КГ №35) я рассказал об истории развития ajax, о том, как поддержка методов подзагрузки информации в страницу проделала путь от специфической технологии, поддерживаемой только internet explorer, до общепризнанного стандарта в Web. Я рассказал о старых методиках, применявшихся до того, как поддержка ajax добавилась в opera9 и firefox. Я упомянул о существовании библиотеки Д. Котерова и ее подходе к реализации ajax. Сегодня я расскажу вам о библиотеках иного плана. Их цель — не обеспечить "просто отправку и загрузку данных" а, именно обеспечить высокоуровневый слой-посредник между javascript-кодом, который исполняется в вашем браузере, и php-кодом, работающим на веб-сервере. Причем, и это важно, такая коммуникация должна быть прозрачной, или незаметной — т.е. обеим сторонам будет казаться, что вызываемые ими функции находятся "вот здесь, рядом", а не за тысячи километров. В прошлый раз я закончил на том, что показал, как именно можно выполнить ajax-вызов и загрузить в страницу некоторый код xml. Затем данный код анализировался, и его содержимое применялось для изменения внешнего вида страницы. Я опустил вопрос о том, откуда взялся этот самый xml, кто его сформировал. Интуитивно нам все ясно, что код должен был быть сгенерирован некоторым php-скриптом посредством множества вызовов функций print 'еще кусочек xml'. Например, так:

<?php
header ('content-Type: text/xml');
// важно не забыть указать тип возвращаемой информации
print ('<?xml version="1.0" encoding="windows-1251"?>' . "\n");
print ('<result><color>red</color><message>Запрос выполнен успешно</message></result>' . "\n");
?>

Наверное, здесь вообще не стоило останавливаться. Благо единственная сложность, которая вас ждет, — при выводе xml не ошибиться в записи собственно тегов — таких вольностей (перепутанные и незакрытые теги, беспорядок с регистром символов), как html, xml не позволяет. Можно построить дерево xml с помощью специальных функций вроде: "createElement, appendchild" и т.д., затем данное дерево с помощью, опять-таки, служебной функции php будет преобразовано в текстовый вид. На этом пути вас ждут проблемы с тем, какую библиотеку для работы с xml использовать (в мире php в настоящее время можно насчитать добрый пяток разномастных библиотек, зачастую несовместимых между собой). Можно использовать не встроенные в php функции для работы с xml, а пользовательские библиотеки — например, domit. Их особенность в том, что они реализованы не на языке c/c++ в виде библиотек dll, а на самом php — это дает огромные потери по скорости работы, но и независимость от особенностей используемого хостинга. Но все же главная проблема — необходимость преобразования данных из формата, которым оперирует php — массивы, объекты, простые скалярные переменные — в тот формат, который был бы способен понять браузер и javascript. На стадии приема данных из скрипта javascript для последующей обработки внутри php-кода стоит та же проблема — необходимость уже двухшаговой конвертации данных. Смотрите: данные, которые мы хотим послать из браузера, хранятся в некоторой структуре, понятной javascript — массивы, объекты, скаляры. Мы их кодируем в формат xml и пересылаем на сервер.

Там выполняется преобразование из формата xml во множество переменных php — тех же массивов, объектов и скаляров. Возникает вопрос: а можно ли исключить из этой цепочки xml и обеспечить прямую передачу данных из javascript в php? Естественно, этим вопросом озаботились достаточно давно, и среди множества библиотек поддержки ajax можно найти те, которые выполняют такое преобразование незаметно для нас. В основе своей все равно идет передача xml по сети, но мы об этом просто не знаем. Перед тем, как я покажу одну из подобных библиотек, стоит упомянуть об ajaj. Термины ajax и ajaj отличаются только одной последней буквой — собственно, она кодирует то, какой формат будет использован для передачи данных. На самом деле, когда вы выполняете запрос из страницы, то формат может быть любым — абсолютно — стандарт httр почти никак это не ограничивает. Главное — чтобы этот формат могли понять на той стороне — сервере. Просто так исторически сложилось, что первым на эту роль пришел xml. Xml давно и заслуженно (зачастую переигрывая) критикуют за сложность. Все эти скобки, вложенные-переложенные теги пугают молодых неофитов. Да и действительно, xml слишком универсален и не удобочитаем. В ситуациях, когда передаваемые данные невелики и представляют собой простое перечисление элементов или набор свойств некоторого объекта, xml выглядит слишком громоздко. Поэтому некоторое время назад появился язык yaml, который пытаются позиционировать как замену xml. Уверен, что очередной революции не случится. Yaml занял свою скромную нишу как язык написания несложных конфигурационных файлов, как формат передачи небольших и не очень сложноструктурированных документов, но на большее он не способен. На этом отступление от темы я закрою, а теперь собственно вопрос. А что, нет такого формата, в который можно было бы легко перекодировать данные из javascript-кода? А затем, хотя это и не столь обязательно, можно было бы быстро прочитать и разобрать в php-коде. Конечно, такой формат есть! Он называется json. Вот та последняя буква в аббревиатуре ajaj — asynchronous javascript and json. JSON — это еще одна аббревиатура, раскрывающаяся как javascript object notation. Эта форма нотации позволяет в достаточно компактной и удобочитаемой форме записать сложную структуру данных в javascript или flash actionscript . Родной поддержки json в php нет, но, к счастью, в php5 появились функции для быстрого (это важно) разбора json переменных и преобразования их в переменные в стиле php, и наоборот. На всякий случай предварительно проверьте, включена ли поддержка json у вас на хостинге. Так, создав файл php, который содержит вызов только одной функции "phpinfo ();", можно получить подробное перечисление поддерживаемых расширений (см. рис. 1). А теперь пример использования json-функций:

<?php // исходная информация для последующего кодирования — обратите внимание,что мы используем сложные типы данных:
// ассоциативный массив, содержащий вложенный массив объектов (payments)
$x = array ( 'fio' => 'bill makkenzi',
'birthdate' => '2000.1.1', 'payments' => array (
array ('dateof' => '2001.1.1', 'balance' => 3000, 'currency' => 'EUR'),
array ('dateof' => '2002.1.1', 'balance' => 5700, 'currency' => 'USD'),
array ('dateof' => '2003.7.1', 'balance' => -600, 'currency' => 'EUR')
) );
print '<h1>Сначала JSON из PHP-объекта</h1>';
print json_encode ($x);// создаем строку с json-нотацией
print '<h1>А теперь PHP из JSON-строки</h1>';
print '<pre>';// выполняем раскодирование json-строки в переменную php
print_r (json_decode (json_encode ($x)));
print '</pre>'; ?>

Результат работы скрипта показан на рис. 2.
А для разбора json-строки в коде javascript достаточно сделать вызов функции eval (функция вычисляет переданный ей аргумент — строку с json- нотацией). Последнее — закодировать данные из javascript в json-строку текста для последующей отправки серверу — и цикл замкнется. Давайте разберем это на примере библиотеки jquery — я писал о ней в прошлых сериях, хотя большей частью сосредоточился на том, какие плюсы jquery дает для работы с деревьями DOM, с событиями элементов. Теперь осталось рассмотреть предпоследнюю главную возможность jquery — поддержку ajax. Задача будет проста: javascript-код делает вызов некоторого файла php, читающего размещенный на сервере файл, каждая строка которого — например, имя пользователя, оставившего сообщение, и собственно текст сообщения в гостевой книге сайта — разделены символом ":". Данные кодируются в json- формат, затем отправляются в браузер, где строится html-таблица с перечнем этих сообщений. Сначала пример файла с данными (baza.txt):

Vasya Tapkin: It's Super Site.
George Tailor: It's Very Bad Site.
Mary Tompkins: I know nothing about this site.

Теперь код файла php. Обратите внимание, что в качестве параметра этому файлу передана переменная page_size, управляющая тем, какое количество первых сообщений из файла следует вернуть:

<?php
$plaindata = file ('baza.txt');
// прочитали файл с сообщениями
$result = array ();
// входная переменная, управляющая работой скрипта, — количество сообщений, которые нужно вернуть
$page_size = $_REQUEST ['page_size'];
for ($i = 0; $i < min($page_size, count($plaindata)); $i++){
// в цикле накапливаем пары (кто, сообщение) в массиве
list ($user, $msg) = explode (':', $plaindata [$i]);
$result [] = array ('user'=>$user, 'msg' => $msg);
}
// здесь формируем окончательный массив с данными, отправляем его в кодированной форме браузеру
print json_encode (array ('filesize' => filesize ('baza.txt'), 'messages' => $result));
?>

Перед примером кода javascript пара слов о поддержке ajax в jquery. Функция, выполняющая удаленный вызов, называется $.ajax(). Она получает в качестве параметра только одну переменную — ассоциативный массив, содержащий множество ключей, управляющих тем, как именно будет выполнен запрос, какие данные и в каком формате должны уйти и вернуться назад, а также функции, которые будут вызваны, чтобы известить о завершении процесса обмена информацией. В таблице я перечисляю некоторые из этих ключей:


КлючПримечание
asyncБулева переменная — кодирует признак того, будет ли сделан удаленный вызов как синхронный или асинхронный. Если вызов синхронный, то на момент выполнения запроса браузер будет заблокирован.
beforeSendФункция, которая вызывается перед отправкой запроса. Ее назначение — выполнить окончательную донастройку объекта XMLhttрRequest, находящегося внутри jquery.
completeФункция вызывается после завершения вызова, когда данные были возвращены, и независимо от того, успешно или нет произошел ajax-вызов.
contentTypeСообщение серверу, что текстовые данные кодируются следующим образом.
dataСобственно данные, которые отправляются серверу — это может быть как строка текста, так и массив, объект или их комбинация.
dataTypeОчень важный параметр — управляет тем, в каком формате данные будут возвращены сервером. Возможны следующие значения: xml, html, script, json.
errorФункция, вызываемая в том случае, если удаленный вызов был неуспешен.
processDataВозможность отключить кодирование данных перед отправкой. Очень специфическая функция — лучше не трогайте.
successФункция, вызываемая, когда запрос был успешно завершен.
timeoutПеременная, кодирующая интервал времени, в течение которого мы ждем завершения удаленного вызова. Задается в миллисекундах.
typeТип запроса: GET или POST. Остальные методы — HEAD, PUT — не поддерживаются.
urlСамое главное — адрес страницы php, которую следует запустить.


А теперь код примера, загружающего книгу обратных отзывов. В теле страницы присутствует блок div, куда будет помещена создаваемая таблица сообщений, также текстовое поле, где будет указываться число — максимальный размер возвращенной таблицы сообщений:

<html><head>
<script type="text/javascript" language="javascript" src="jquery.js"> </script>
<script>
function sender (){
$.ajax({
type: "POST", url: "bazareader.php", dataType : 'json',
data: {page_size: $('#page_size').attr('value') },
success: function(msg){
var oRow = null;
var ocell = null;
alert( "File Length: " + msg.filesize );
var t = document.createElement ('table');
// создаем первую строку таблицы с заголовками — названиями столбцов
oRow = t.insertRow(0);
// создаем ячейку строки
ocell = oRow.insertcell(0);
// и указываем содержимое ячейки
ocell.innerHTML = 'User';
ocell = oRow.insertcell(1);
ocell.innerHTML = 'Message';
// организуем цикл по массиву сообщений
for (var i = 0; i < msg.messages.length; i++){
// создаем очередную строку
oRow = t.insertRow(i + 1);
// в нее помещаем две ячейки — для имени пользователя
ocell = oRow.insertcell(0);
ocell.innerHTML = msg.messages[i].user;
// и для текста сообщения
ocell = oRow.insertcell(1);
ocell.innerHTML = msg.messages[i].msg;
}//for
document.getElementById('dv_Result').appendchild (t); } });
}// конец ajax-вызова
</script> </head><body>
<div id="dv_Result" style="">Result ...</div>
<br />Page Size: <input type="text" id="page_size" value="10" /><br />
<input type="button" onclick="sender ()" value="click me !"/>
</body></html>

Результат работы скрипта показан на рис. 3. Естественно, в качестве источника данных может выступать и произвольный скрипт — например, далее я выполняю загрузку с сайта tut.by новостей в формате rss. Данный формат основан на xml (следовательно, все, что мне нужно, — при вызове ajax указать значение свойства dataType как "xml") и выглядит примерно так:

<?xml version="1.0" encoding="windows-1251"?>
<rss version="2.0">
<channel>
<title>Новости TUT.BY Главные новости</title>
<link>httр://news.tut.by/</link>
<description>Новости TUT.BY</description>
<item>
<title>Заголовок новости</title>
<link>httр://news.tut.by/94435.html</link>
<description>Краткое описание новости</description><pubDate>Mon, 17 Sep 2007 08:40:00 +0300</pubDate>
<guid>httр://news.tut.by/94435.html</guid>
</item></channel></rss>

Здесь корневой тег "rss" содержит тег "channel", играющий роль контейнера для произвольного количества элементов "item" — новость. Внутреннее устройство новости очевидно: заголовок, собственно текст новости, ссылка на страницу "подробнее" и дата публикации. Последнее свойство guid играет роль уникального идентификатора новости, в примере здесь выбран адрес страницы с текстом новости. Теперь пример с javascript, который загружает данную ленту новостей rss и выполняет ее разбор, затем идет конструирование таблицы, каждая строка которой — новость. Еще момент: если мы делаем ajax-вызов к содержимому, расположенному не в нашем домене (например, ваш скрипт размещен на сайте abc.by, а вызов идет к ленте на tut.by), то мы столкнемся с политикой безопасности, регламентирующей кроссдоменные вызовы. Так, для internet explorer код, который я приведу далее при попытке открытия удаленного адреса, приведет к появлению на экране диалогового окна, где мы должны подтвердить, что действительно согласны выполнить загрузку данных из другого домена. Этот параметр можно изменить в окне настроек, как показано на рис. 4. Для firefox, opera все гораздо хуже — специальных параметров настроек для управления этим не предусмотрено. Но в любом случае можно воспользоваться стандартной методикой обмана: создается файл-посредник. Именно он вызывается из javascript и, в свою очередь, выполняет чтение ленты rss с другого сайта (в php ограничения на crossdomain-вызовы помягче). Вот пример такого файла-посредника:

<?php
// важно правильно указать тип заголовков: что именно мы возвращаем
header('content-Type: application/rss+xml');
$f = fopen ('httр://news.tut.by/rss/index.rss', 'r') or die ('cannot open file');
while (!feof($f)) { print fread($f, 8192); }
fclose($f);
?>

И, наконец, код, который выполняет чтение новостной ленты и превращает ее в таблицу:

function sender (){
$.ajax({ type: "POST", url: "fromby.php",
success: function(msg){
var title = msg.getElementsByTagName ('title') [0];
title = title.firstchild.nodeValue;
alert ('Загружена новостная лента: ' + title);
// получаем список всех новостей в ленте
var items = msg.getElementsByTagName ('item');
var t = document.createElement ('table');
oRow = t.insertRow(0);
// создаем заголовки таблицы
ocell = oRow.insertcell(0).innerHTML= 'Заголовок';
ocell = oRow.insertcell(1).innerHTML= 'Новость';
ocell = oRow.insertcell(2).innerHTML= 'Ссылка';
for (var i = 0; i < items.length; i++){
var rss_item = items[i];
// извлекаем из документа xml набор тегов
var i_title = rss_item.getElementsByTagName ('title')[0].firstchild.nodeValue;
var i_link = rss_item.getElementsByTagName ('link')[0].firstchild.nodeValue;
var i_description = rss_item.getElementsByTagName ('description')[0].firstchild.nodeValue;
oRow = t.insertRow(i+1);
// создаем заголовки таблицы
ocell = oRow.insertcell(0).innerHTML= i_title;
ocell = oRow.insertcell(1).innerHTML= i_description;
ocell = oRow.insertcell(2).innerHTML= i_link;
}
document.getElementById('dv_Result').appendchild (t)} });
}// конец ajax-вызова

В следующий раз я завершу рассказ об ajax. Нас ждет библиотека hajax и рассказ о том, как можно отправлять на сервер не только простые текстовые данные, но и файлы.

black zorro, black-zorro.jino-net.ru


Компьютерная газета. Статья была опубликована в номере 36 за 2007 год в рубрике программирование

©1997-2024 Компьютерная газета