JSHttpRequest: динамическая подкачка данных без перезагрузки страницы
В данной статье описывается подход к Remote Scripting, реализацию которого я до сих пор не видел ни на одном сайте. Более того, мне не известен ни один человек, который бы о нем знал заранее. Есть шанс, что метод уникален, поэтому, если (в особенности если!) это не так, пожалуйста, сообщите в форум, где и когда вы видели его реализацию.
В последние несколько месяцев определенную популярность получил новый сервис Google: так называемый Google Suggest. Те, кто еще не видел, что это такое, могут посмотреть прямо сейчас: http://www.google.com/webhp?complete=1&hl=en.
Работа Google Suggest заключается в том, что по нескольким введенным буквам специальная программа на JavaScript обращается к сайту Google и запрашивает у него 10 самых «популярных» слов, начинающихся с тех же букв. Скрипт срабатывает настолько быстро, что выпадающий список с вариантами появляется практически мгновенно. Естественно, перезагрузка страницы при этом не производится — все реализовано на JavaScript и DHTML.
Для реализации «динамической подгрузки» Google использует следующие средства:
- В Internet Explorer: ActiveX-компонента с именем Msxml2.XMLHTTP или Microsoft.XMLHTTP.
- В Mozilla и FireFox: встроенный класс XMLHttpRequest.
- В Opera: динамически создаваемый
<IFRAME> нулевого размера (скрытый).
Про то, как работает Google Suggest, в Интернете пишут все, кому не лень, и я совершенно не собираюсь повторяться. Вместо этого я представлю новый подход под названием
Начнем с примера
Чтобы вам не было чересчур скучно, я сразу демонстрирую библиотеку
Забегая вперед, скажу, что
Недостатки подхода Google Suggest
Итак, в разных браузерах Google применяет совершенно различные методы подгрузки. Рассмотрим их недостатки.
- Т.к. в IE используется ActiveX-компонента, вы должны включить ActiveX в настройках браузера. И хотя по умолчанию данная функция как раз включена, многие пользователи, наслышанные о многочисленных дырах IE, вручную ее отключают.
Несмотря на это, вы все же можете убедиться, что Google Suggest продолжает работать и после выключения ActiveX. Видимо, задействуется механизм, основанный на
<IFRAME> (как в Opera). В любом случае, на ваших сайтахMicrosoft.XMLHTTP при выключенных ActiveX работать не будет (это проверено). Про недостатки<IFRAME> -метода сказано ниже. - Класс
XMLHttpRequest , используемый в Mozilla и FireFox, в настоящий момент присутствует только в этих браузерах, и больше нигде. У него есть небольшой недостаток: при умолчательных настройках FireFox запрещено загружать данные откуда-то, кроме как с текущего сайта. - Применение динамически создаваемого
<IFRAME> связано с массой проблем. Главный недостаток — при изменении атрибутаsrc у<IFRAME> раздается характерный щелчок и добавляется запись в «историю браузера», так что кнопка Back (Назад) начинает работать неправильно. И хотя данный «подводный камень» можно обойти (весьма искусственным способом), возникают новые проблемы, различные в разных браузерах. Я не буду их сейчас перечислять; скажу только, что за 2 дня перепробовал множество (штук 20) всевозможных вариантов, но добиться кроссбраузерного кода, работающего одинаково и без посторонних эффектов во всех браузерах, мне так и не удалось. Другой недостаток<IFRAME> — большой расход памяти и медлительность: фактически, для каждого фрейма создается новый отдельный браузер, который независимо обрабатывает загруженный HTML-код.
Короче говоря, Google использует разные (ортогональные, несовместимые) подходы в различных браузерах.
Метод, который реализует динамическую подгрузку в Google Suggest, проиллюстрирован ниже на примере загрузки исходного текста текущей страницы. (Работу с
<script>
function doLoad() {
var req = window.XMLHttpRequest?
new XMLHttpRequest() :
new ActiveXObject("Microsoft.XMLHTTP");
req.onreadystatechange = function() {
if (req.readyState == 4)
alert('Loaded:\n'+req.responseText);
}
req.open("GET", document.location, true);
req.send(null);
}
</script>
<input type="button" value="Show me" onclick="doLoad()">
Этот код будет работать только в Mozilla (FireFox), а также в IE (при включенных ActiveX). Opera, а также пользователи, выключившие себе ActiveX по соображениям безопасности, «отдыхают».
Принцип работы библиотеки JSHttpRequest
Многочисленных проблем и особенностей с ActiveX,
Дело в том, что существует один прекрасный и крайне кроссбраузерный способ загрузки данных на страницу. Очень странно, что разработчики Google до него не догадались. Речь о динамическом создании и присоединении к текущей странице тэга
Конечно, загружаемый скрипт должен выдавать корректный код на JavaScript. Обычный текст таким методом не подгрузишь.
Рассмотрим на примере, как работает данный подход. Предположим, что при нажатии на кнопку JavaScript-программа вставляет (c использованием DHTML) в текущую страницу следующий тэг:
Листинг 2
<script language="JavaScript"
src="load.php?123:arguments"></script>
Что при этом произойдет? Браузер немедленно обратится к серверу со следующим запросом:
Листинг 3
load.php?123:arguments
В результате на сервере запустится скрипт
Листинг 4
JSHttpRequest.dataReady(
123,
[
'Это некоторые данные.',
'Они могут иметь произвольную структуру...',
{ test: '...и вложенность' }
],
'А здесь идет простой отладочный текст.'
)
Если вы не поняли, к чему все эти квадратные и фигурные скобки, скорее прочитайте тридцать восьмую наблу, в которой рассматривуются особенности синтаксиса JavaScript.
Итак, PHP-скрипт
М-ммм... «Программа, пишущая другие программы»... «Источник»... Определенно «Matrix has you».
В итоге код на JavaScript, сгенерированный PHP-скриптом
- Уникальный идентификатор загрузки (чтобы не спутать одни данные с другими, ведь страница может одновременно запросить сведения сразу из нескольких источников).
- Произвольные данные, полученные программой
load.php , например, из БД. - Некоторый текст, который может быть использован в отладочных целях (например, там удобно указывать сообщения об ошибках, возникших в PHP-программе).
Ну а уж функция
Динамическая генерация тэга
Библиотека JSHttpRequest состоит из двух частей, работающих совместно друг с другом:
- JSHttpRequest.js, 3 КБ: JavaScript-код, определяющий класс
JSHttpRequest .Это — так называемый frontend системы («передний проход»). Его следует подключать к страницам с помощью тэга:Листинг 5
<script language="JavaScript" src="JSHttpRequest.js"> </script>
- JSHttpRequest.php, 3.5 КБ: PHP-код, в котором определяются функции для облегчения написания загрузчиков на PHP.
Это — так называемый backend системы («задний проход»). Его следует включать в самое начало программы оператором:Листинг 6
require_once "JSHttpRequest.php";
В качестве языка для написания загрузчиков выбран PHP, потому что он:
- Весьма распространен.
- Крайне быстр, если приходится работать с маленькими скриптами, коими как раз и являются загрузчики. (Это, естественно, касается только
mod_php — он так чаще всего и ставится хостерами.) - В большинстве случаев имеет встроенную поддержку Unicode (расширение
iconv ), которая, как вы увидите ниже, нам очень понадобится.
Можно, конечно, писать скрипты загрузки и на CGI-perl, однако в этом случае нагрузка на сервер резко возрастет, что для динамической подгрузки данных противопоказано. Ну а
JSHttpRequest: JavaScript-frontend
Использовать класс
Приведу пример страницы, которая обеспечивает генерацию хэш-кода MD5 для введенной пользователем строки. Само вычисление происходит на сервере, а браузер лишь обращается к последнему за данными, используя библиотеку
<script language="JavaScript"
src="../JSHttpRequest.js"></script>
<script>
// Вызывается по тайм-ауту или при щелчке на кнопке.
function doLoad(force) {
// Получаем текст запроса из <input>-поля.
var query = '' + document.getElementById('query').value;
// Создаем новый объект JSHttpRequest.
var req = new JSHttpRequest();
// Код, АВТОМАТИЧЕСКИ вызываемый при окончании загрузки.
req.onreadystatechange = function() {
if (req.readyState == 4) {
// Записываем в <div> результат работы.
document.getElementById('result').innerHTML =
'MD5("'+req.responseJS.q+'") = ' +
'"' + req.responseJS.md5 + '"';
// Отладочная информация.
document.getElementById('debug').innerHTML =
req.responseText;
}
}
// Разрешаем кэширование (чтобы при одинаковых запросах
// не обращаться к серверу несколько раз).
req.caching = true;
// Подготваливаем объект.
req.open('GET', 'load.php', true);
// Посылаем данные запроса (задаются в виде хэша).
req.send({ q: query });
}
// Поддержка загрузки данных по тайм-ауту (1 секунда после
// последнего отпускания клавиши в текстовом поле).
var timeout = null;
function doLoadUp() {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(doLoad, 1000);
}
</script>
<!-- Форма -->
<form onsubmit="return false">
<input type="text" id="query" onkeyup="doLoadUp()">
<input type="button" onclick="doLoad(true)" value="load">
<br><i>Введите "error", чтобы протестировать отладочные
возможности библиотеки.</i>
</form>
<!-- Результаты работы (заполняется динамически) -->
<div id="result" style="border:1px solid #000; margin:2px">
Results
</div>
<!-- Отладочная информация (заполняется динамически) -->
<div id="debug" style="border:1px dashed red; margin:2px">
Debug info
</div>
Из-за обилия комментариев выглядит страшно, однако, если внимательно посмотреть, хорошо видно, что применение
Имеется одна важная особенность библиотеки: результат работы
{
q: 'запрос',
md5: 'MD5-код введенной строки'
}
В поле
Впрочем, ничто не мешает написать загрузчик так, чтобы он передавал основной результат своей работы именно в виде
JSHttpRequest: PHP-backend
Теперь пришло время посмотреть, как выглядит загрузчик
Но взгляните на код загрузчика. Вы увидите, что всех описанных выше проблем в нем попросту не возникает! (Этот же PHP-скрипт.)
Листинг 9
<?php
// Подключаем библиотеку поддержки.
require_once "../JSHttpRequest.php";
// Создаем главный объект библиотеки.
// Указываем кодировку страницы (обязательно!).
$JSHttpRequest = new JSHttpRequest("windows-1251");
// Получаем запрос.
$q = $_REQUEST['q'];
// Формируем результат прямо в виде PHP-массива!
$_RESULT = array(
"q" => $q,
"md5" => md5($q)
);
// Демонстрация отладочных сообщений.
if (strpos($q, 'error') !== false) {
callUndefinedFunction();
}
?>
Итак, библиотека
К счастью, массивы и хэши JavaScript и PHP устроены практически одинаково, поэтому можно безболезненно производить перевод PHP-массива...
Листинг 10
$_RESULT === array(
"q" => 'запрос',
"md5" => 'MD5-код введенной строки'
)
...в JavaScript-хэш:
Листинг 11
req.responseJS === {
q: 'запрос',
md5: 'MD5-код введенной строки'
}
Ну а чтобы все окончательно прояснилось, приведу примерный результат
работы скрипта
Листинг 12
JSHttpRequest.dataReady(
123,
'Отладочные сообщения.',
{
q: 'запрос',
md5: 'MD5-код введенной строки'
}
)
Итак, простое присваивание значения массиву
Перехват ошибок в PHP-загрузчике
«Обертывание» работает с использованием функции PHP ob_start(), которая позволяет перехватывать данные, поступающие в выходной поток скрипта, и производить с ними любые преобразования. По счастливой случайности,
Кстати, эта полезная особенность функции
Т.к. все сообщения об ошибках (например, вызов несуществующей функции) PHP печатает прямо в выходной поток (как будто бы через
Вы можете убедиться, что перехват ошибок работает, введя на тестовой странице (см. выше) строчку, содержащую слово
Листинг 13
Fatal error: Call to undefined function: callundefinedfunction()
in load.php on line 15
Решение проблемы с кодировками
При формировании запроса к загрузчику может потребоваться передать ему строки, содержащие русские буквы. Естественно, их нельзя напрямую передавать в URL, а вначале нужно URL-
В JavaScript имеется функция
В то же время,
Вообще говоря, в последний версиях JavaScript имеется функция
К счастью, популярное расширение
Итак, вы можете вызывать метод
Конечно, не забудьте, что если вы хотите использовать библиотеку
Еще большая кроссбраузерность?
У меня есть большое подозрение, что трюк с динамической генерацией тэга
Если вы решите модифицировать библиотеку так, чтобы она работала и в старых браузерах, я буду рад получить от вас измененный код, а также обсудить его нюансы в форуме.
Резюме
Подведем итоги этой большой наблы. Вначале я перечислю ссылки на программные модули, упоминаемые выше.
- JavaScript-frontend библиотеки
JSHttpRequest : JSHttpRequest.js. - PHP-backend библиотеки
JSHttpRequest : JSHttpRequest.php.
Пример использования библиотеки:
Библиотека
- "Живой поиск": на каждой странице форума имеется поле, в которое можно ввести поисковый запрос и сразу же получить результат, минуя перезагрузку страницы.
- "Живой поиск" в форме добавления нового топика: то же самое, но срабатывает при вводе темы нового сообщения.
- "Живой предпросмотр": рядом со ссылками на топики приведена специальная пиктограмма, наведя мышь на которую, можно просмотреть первое сообщение топика.
- "Живая карма": пользователи могут изменять карму (рейтинг) друг другу, не перезагружая страницу.
Статьи в Интернете на тему
- Dynamic HTML and XML: The XMLHttpRequest Object.
- XML Extras (in Mozilla).
- Expanded XML Support in Internet Explorer 5.
- Remote Scripting with IFRAME.
- Результаты поиска в Google: XMLHttpRequest, remote scripting.