Подобно любой другой архитектуре, Ajax имеет свои ловушки. В этом разделе описываются три основные проблемы, о которых следует знать при разработке Ajax_приложений.
Первая проблема – визуальная обратная связь. Когда пользователь щелкает на традиционной гиперссылке, веб_броузер обеспечивает индикацию процесса за_ грузки содержимого ссылки. Такая обратная связь предоставляется еще до того, как содержимое будет готово к отображению, поэтому пользователь явно видит, что броузер работает над выполнением его запроса. Однако, когда HTTP_запрос запускается из объекта XMLHttpRequest, броузер не предоставляет никакой обрат_ ной связи. Даже при подключении к магистральным линиям инерционность сети часто вызывает заметные задержки между посылкой HTTP_запроса и получением ответа. Поэтому для Ajax_приложений особенно важно обеспечивать визуальную обратную связь (например, в виде простой DHTML_анимации; подробнее об этом см. в главе 16), пока приложение ожидает получение ответа от XMLHttpRequest.
Обратите внимание: в примере 20.8 не учитывается совет по обеспечению визу_ альной обратной связи просто потому, что в данном примере для запуска HTTP_ запроса пользователь не предпринимает никаких активных действий. Запрос выполняется, когда пользователь (пассивно) наводит указатель мыши на ссыл_ ку. Пользователь явно не требует от приложения выполнить какое_либо дейст_ вие и потому не нуждается в обратной связи.
Вторая проблема связана с URL. В традиционных веб_приложениях переход из одного состояния в другое сопровождается загрузкой новой страницы, причем каждая страница имеет свой уникальный URL_адрес. Это не относится к Ajax_ приложениям: когда Ajax_приложения используют протокол HTTP для загруз_ ки и отображения нового содержимого, URL в адресной строке не изменяется. У пользователя может появиться желание сделать закладку на приложение
20.3. Ajax и динамические сценарии
в конкретном состоянии, но с помощью механизма закладок броузера сделать это будет невозможно. Более того, пользователю не поможет даже копирование URL из адресной строки броузера.
Эта проблема и ее решение прекрасно иллюстрирует приложение Google Maps (http://local.google.com). При изменении масштаба карты или ее прокрутке меж_ ду клиентом и сервером передаются огромные объемы информации, но URL_ад_ рес, отображаемый в адресной строке броузера, не изменяется. Компания Google решила проблему установки закладок, добавив в каждую страницу ссылку «link to this page» (ссылка на эту страницу). Щелчок на этой ссылке генерирует URL_ адрес на отображаемую в данный момент карту и вызывает перезагрузку страни_ цы с этим URL_адресом. После того как загрузка завершится, ссылка на карту в текущем состоянии может быть помещена в закладки, отправлена по элек_ тронной почте и тому подобное. Главное, что должны извлечь разработчики из этого урока: все, что имеет существенное значение для описания состояния веб_ приложения, должно инкапсулироваться в URL_адресе и этот URL_адрес дол_ жен быть доступен пользователю в случае необходимости.
Третья проблема, которая часто упоминается при обсуждении Ajax, связана с кнопкой броузера Назад. Отобрав у броузера контроль над протоколом HTTP, сценарии, использующие объект XMLHttpRequest, обходят механизм хранения ис_ тории броузера. Пользователи привыкли применять кнопки Назад и Вперед для навигации по Всемирной паутине. Если Ajax_приложение средствами HTTP по_ лучает и отображает существенные объемы содержимого документа, пользова_ тели могут попробовать с помощью этих кнопок перемещаться между различны_ ми состояниями приложения. Но когда они попробуют щелкнуть на кнопке На_ зад, то с удивлением обнаружат, что эта кнопка отправляет их за пределы прило_ жения, вместо того чтобы вернуть к его предыдущему состоянию.
В свое время неоднократно предпринимались попытки решить проблему кнопки Назад за счет добавления URL_адресов в историю броузера. Однако, как правило, эти попытки увязали в трясине программного кода, учитывающего специфиче_ ские особенности каждого броузера, и не давали достаточно удовлетворитель_ ных результатов. И даже когда удавалось добиться положительных результа_ тов, найденные решения противоречили основной парадигме Ajax и вынуждали пользователя полностью перезагружать страницы, вместо того чтобы обеспечить прозрачное взаимодействие с сервером по протоколу HTTP.
На мой взгляд, проблема кнопки Назад не настолько серьезна, как ее пытаются представить, и ее отрицательное влияние может быть минимизировано за счет тщательного обдумывания дизайна веб_приложения. Элементы приложения, похожие на гиперссылки, должны вести себя как гиперссылки и действительно должны вызывать перезагрузку страницы. Это сделает их субъектами механиз_ ма истории броузера, как того и ожидает пользователь. Те же элементы прило_ жения, которые инициируют взаимодействия с протоколом HTTP в обход меха_ низма истории броузера, наоборот, не должны напоминать гиперссылки. Вер_ немся к приложению Google Maps еще раз. Когда пользователь прокручивает карту в окне броузера, он не ожидает, что кнопка Назад сможет отменить опера_ цию прокрутки, точно так же, как он не ожидает, что кнопка Назад отменит опе_ рацию прокрутки обычной веб_страницы в окне броузера.
516 Глава 20. Работа с протоколом HTTP
Следует с особой осторожностью подходить к использованию слов «вперед» и «назад» в Ajax_приложениях для обозначения внутренних элементов управле_ ния навигацией. Например, если интерфейс приложения реализован в стиле многостраничного мастера с кнопками Вперед и Назад для отображения следую_ щего или предыдущего экрана, оно должно поддерживать традиционный способ загрузки страниц (вместо XMLHttpRequest), потому что в такой ситуации пользова_ тель вполне оправданно ожидает, что кнопка броузера Назад будет вызывать точ_ но такой же эффект, что и кнопка Назад в приложении.
В более широком смысле кнопка броузера Назад не должна восприниматься как кнопка Отмена в приложении. Ajax_приложения могут предусматривать собст_ венную реализацию операций повторить/отменить, если они будут востребова_ ны пользователем, но они должны совершенно четко отличаться от функций, выполняемых кнопками Назад и Вперед броузера.
20.4. Взаимодействие с протоколом HTTP с помощью тега <script>
В броузерах Internet Explorer версий 5 и 6 объект XMLHttpRequest представляет со_ бой ActiveX_объект. Иногда из соображений безопасности пользователи запре_ щают применение ActiveX_объектов в Internet Explorer, и в такой ситуации сце_ нарии лишены возможности создавать объекты XMLHttpRequest. В случае необхо_ димости можно будет выполнять простые HTTP_запросы GET с помощью тегов <iframe> и <script>. Несмотря на то, что таким способом невозможно реализовать все функциональные возможности объекта XMLHttpRequest,1 тем не менее удастся реализовать, по меньшей мере, вспомогательную функцию HTTP.getText(), кото_ рая работает без привлечения ActiveX.
Сгенерировать HTTP_запрос достаточно просто с помощью свойства src тегов <script> и <iframe>. Но гораздо сложнее извлечь данные из этих элементов без из_ менения этих данных броузером. Тег <iframe> ожидает, что в него будет загружен HTML_документ. Если попытаться загрузить в плавающий фрейм простой текст, вы обнаружите, что текст будет преобразован в формат HTML. Кроме того, неко_ торые версии Internet Explorer некорректно реализуют обработку событий onload и onreadystatechange в теге <iframe>, что еще больше осложняет ситуацию.
Подход, который здесь рассматривается, основан на использовании тега <script> и сценария на стороне сервера. В этом случае серверному сценарию сообщается URL_адрес, содержимое которого требуется получить, и имя функции на сторо_ не клиента, которой это содержимое должно быть передано. Серверный сцена_ рий берет содержимое с требуемого URL_адреса, преобразует его в JavaScript_ строку (возможно, достаточно длинную) и возвращает клиентский сценарий, ко_ торый передает эту строку указанной функции. Поскольку данный клиентский сценарий загружается в тег <script>, по окончании загрузки указанная функция вызывается автоматически.
В примере 20.9 приводится реализация серверного сценария на языке PHP.
1 Полная замена объекта XMLHttpRequest, вероятно, потребует применения Java_ апплета.
20.4. Взаимодействие с протоколом HTTP с помощью тега <script>
Пример 20.9. jsquoter.php
<?php
// Указать броузеру, что выполняется передача сценария header("Content_Type: text/javascript");
// Извлечь аргументы из URL
$func = $_GET["func"];
// Функция вызова нашего JavaSript_кода
$filename = $_GET["url"];
//
Файл или
URL для передачи функции
func
$lines = file($filename);
//
Получить
строки содержимого файла
$text = implode("", $lines); // Объединить их в одну строку // Экранировать кавычки и символы перевода строки
// Отправить все это в виде одиночного вызова JavaScript_функции echo "$func('$escaped');"
?>
Клиентская функция в примере 20.10 использует серверный сценарий jsquo' ter.php из примера 20.9 и работает на манер функцииHTTP.getText()из приме_ра 20.2.
Пример 20.10. Вспомогательная функция HTTP.getTextWithScript()