
Ajax в действии
.pdf
Глава 12. "Живой" поиск с использованием XSLT 481
Рис. 12.1. Классическая модель поиска с обозначенными этапами обработки
проекта, описанное решение может порождать проблемы — потерю состояния страницы, очистку полей формы и т.д. На рис. 12.1 приведена диаграмма классической модели поиска, когда вся страница отправляется на сервер для обработки, а возвращается совершенно новая страница с результатами
Одним из источников задержек является то, что запросы к базе данных могут обрабатываться довольно долго. Действия с браузером недоступны пользователю до тех пор, пока не будут отображены результаты, при этом страница кажется "зависшей". Иногда разработчики пытаются как-то бороться с данным периодом неактивности, например, уведомлять пользователя о том, что процесс идет. Здесь важно отметить, что проблема недоступности не ограничивается операциями поиска. С ней можно столкнуться при обновлении или удалении записей из базы данных, запуске сложной транзакции на стороне сервера и т.д.
В качестве средства борьбы с описанным явлением разработчики используют анимированные GIF-изображения (например, заменяющие собой строку состояния), вызываемые в то время, когда сервер обрабатывает отправленный запрос. Вопрос о том, как это можно сделать, является одним из самых популярных на различных форумах, подобных JavaRanch (www.JavaRanch.com). Однако проблемой анимированных GIF-изображений является то, что они запускаются не всегда. В Microsoft Internet Explorer GIF-анимация часто замирает на первой картинке, не демонстрируя циклически предполагаемый набор изображений. Разработчики знают, что некоторые пользователи, не видя анимации, считают, будто браузер "завис", и щелкают на кнопке обновления или закрывают браузер.
Классическая форма поиска подвержена тем же проблемам, которые описывались в предыдущих примерах, требовавших повторной визуализации страницы. Из-за загрузки новой страницы, которая отображается в начале, а не в том месте, до которого была прокручена на момент инициации обновления, информация о прокрутке страницы может теряться. Данные, введенные в поля формы, могут исчезать, и пользователю придется вводить их заново. Разработчики пытаются решать эти проблемы с помощью фреймов и всплывающих окон, но-прл этом порождают еще больше проблем. Что ж, давайте посмотрим, почему это происходит.

Рис. 12.2. Процесс поиска с использованием фреймов
12.1.2.Недостатки использования фреймов
ивсплывающих окон
Для решения проблем кажущегося "зависания" страниц, потери положения прокрутки и т.п. разработчики традиционно применяют фреймы (обычные и IFrame) и всплывающие окна. Фреймы и всплывающие окна позволяют продолжать обработку в другой части Web-страницы, так что пользователь может работать с фрагментом формы, инициировавшим обработку. Причем не только пользователь может манипулировать формой, параллельно могут выполняться и другие функции JavaScript.
Фреймы и всплывающие окна имеют и другие преимущества. Использование фреймов позволяет прокручивать возвращаемый набор записей, оставляя при этом элементы поисковой формы в поле зрения пользователя. Всплывающее окно позволяет отображать результаты в отдельном окне, вынося обработку из основного окна. Правильным образом организовав связь родительского окна с дочерним, можно при возврате результатов передавать данные из дочернего окна в родительское. Всплывающие окна невероятно полезны при внедрении поиска в больших формах, где пользователю требуется определенная трудно запоминаемая информация. Кроме того, можно сделать так, чтобы окно закрывалось после завершения обработки. Это полезно, когда требуется обновить страницу, не передавая на сервер никаких данных.
На рис. 12.2 показано, как реализован поиск во фрейме. Нижний фрейм отвечает за отправку поискового запроса на сервер, позволяя обрабатывать результаты. Поскольку поиск инициировал нижний фрейм, верхний фрейм окна по-прежнему доступен пользователю (в отличие от классической схемы поиска, показанной на рис. 12.1).
Хотя описанные подходы и решают одну проблему, они приводят к возникновению других. Фреймы были и остаются одним из самых страшных кошмаров разработчиков. Основная проблема, связанная с ними, — это навигация, поскольку мы не знаем, как фрейм будет взаимодействовать с браузером. Мы не знаем, как повлияет на фрейм кнопка "Назад". Вернет ли фрейм нас на нужную страницу, уничтожит ли всю структуру фреймов или просто не сработает? Как правило, именно такие вопросы возникают при тестировании. А что произойдет, если открыть страницу в браузере, не поддерживающем структуры фреймов? Чтобы избежать последней проблемы,
Глава 12. "Живой" поиск с использованием XSLT 483
на страницу потребуется включить сценарий детектирования фреймов, из-за чего приложение станет более громоздким, управлять кодом станет сложнее, кроме того, возрастет его общая сложность.
С другой стороны, всплывающие окна можно заблокировать (что пользователи часто и делают). Вообще, всплывающие окна не должны представлять проблем, если они явно инициируются пользователем. Однако они также могут генерироваться браузером автоматически в ответ на событие onload или onunload, причем часто не допускается открывать такие окна, так как они, как правило, используются как реклама. Некоторые пользователи блокируют вообще все всплывающие окна — т.е. они никогда не получат результатов поиска, поскольку необходимое окно не откроется.
При использовании всплывающих окон могут возникнуть и другие проблемы, например, открытие дочернего окна под родительским, — в таком случае всплывающего окна просто не видно. Другая проблема встречается при выполнении некоторых действий в родительском окне. Если пользователь щелкает на ссылке или обновляет страницу, это действие может нарушить связь потомка с родителем, т.е. разрушить сообщение между окнами. При обновлении страницы объект всплывающего окна уничтожается; никаким разумным способом переносить объект со страницы на страницу нельзя.
Как видите, хотя использование фреймов и всплывающих окон решает проблемы, присущие традиционной отправке формы, эти решения могут порождать еще большие проблемы. Впрочем, для решения этих проблем мы можем использовать Ajax. Ajax обрабатывает связь с сервером независимо от страницы браузера, что позволяет воспроизводить анимацию и поддерживать состояние страницы; при этом не нужно заботиться о пользователях, которые блокируют всплывающие окна и закрывают окно, думая, что оно "зависло".
12.1.3. "Живой" поиск с использованием Ajax и XSLT
Функциональные возможности поискового элемента Web-сайта можно улучшить, сделав поиск "живым"; именно так некоторые разработчики называют поиск с использованием Ajax. Для выполнения такого поиска не требуется передавать на сервер всю страницу для обработки (как при традиционном поиске), а это означает, что можно поддерживать текущее состояние страницы. Кроме того, без особых проблем можно запустить JavaScript и GIFанимацию, поскольку результаты отображаются в браузере с помощью tnnerHTML или других методов DOM.
Допустим, что у нас есть средство поиска, инициирующее длительную транзакцию базы данных, в ходе которой страница выглядит недоступной. Используя Ajax, можно запустить анимацию в начале транзакции. Когда придет время выводить результаты, свойству CSS display анимированного изображения можно присвоить значение попе, и анимация просто исчезнет. Можно также поместить анимированное изображение там, куда планируется выводить результаты. После завершения транзакции изображение будет заменено результатами. В любом случае пользователь может использовать форму, в то время как объект XMLHttpRequest обрабатывает данные сервера.

Рис. 12.3. Ход процесса при использовании инфраструктуры Ajax. Процесс, запущенный на стороне сервера, генерирует данные, которые код клиентской части приложения вставляет непосредственно на страницу. Такая реализация процесса требует меньшей полосы пропускания и имеет более дружественный пользовательский интерфейс
Рассмотрим Google Maps — популярный пример того, как пользователь может работать с приложением, пока на сервере выполняется обработка данных. Предположим, что мы отправляем серверу запрос о ресторанах на Главной улице и при этом продолжаем работать с картой, пока сервер его обрабатывает. Нам не нужно ждать, как при обычной отправке формы. Процесс, запущенный на стороне сервера, возвращает результаты на страницу, где они отображаются для пользователя. Точно так же "живой" поиск позволяет пользователю взаимодействовать со страницей, пока сервер обрабатывает данные. Ход данного процесса показан на рис. 12.3.
Использование Ajax для обработки поиска и длительных транзакций позволяет устранить проблемы, с которыми мы сталкивались в прошлом. Возможность "живого" поиска полезна не только при использовании поисковой машины, подобной Google или Yahoo!, но и при потребности в менее масштабном поиске. Например, с помощью "живого" поиска можно обратиться к таблице базы данных и извлечь информацию для одного из полей формы (например, адреса), основываясь на том, что уже ввел пользователь, при этом не мешая пользователю в это время заполнять другие поля. Любую длительную транзакцию с сервером можно превратить в "живой" процесс, когда сервер последовательно и ненавязчиво обновляет информацию, предоставляемую клиенту (см. главу 6). С помощью Ajax передачу данных можно сделать более эффективной и предоставлять результаты клиенту в более богатой среде.
12.1.4. Возврат результатов клиенту
Когда сервер готов вернуть клиенту результат "живого"' поиска, это можно сделать несколькими способами. Результаты можно отформатировать в виде XML-кода, обычного текста или HTML-дескрипторов. В предыдущих примерах на сервере создавался XML-документ. Затем на стороне клиента с помощью JavaScript-кода вызывались методы DOM XML, с помощью которых согласно последовательной обработке узлов XML формировалась таблица результатов. При таком подходе требовалось два цикла. Первый был задействован при формировании XML-докумецта на сервере, второй цикл применялся Для создания HTML-таблицы на стороне клиента.

Глава 12. "Живой" поиск с использованием XSLT 485
Цикла DOM XML на стороне клиента можно избежать, если перед отправкой клиенту сформировать на сервере вместо XML-файла таблицу HTML. Используя такую технологию, мы соединяем дескрипторы HTML в большую строку, подобно тому, как мы формировали XML-документ. Однако теперь вместо дескрипторов XML применяются элементы таблицы. Строка HTML-кода возвращается клиенту, где ее можно присвоить свойству innerHTML элемента. В данном случае использцется свойство responseText объекта XMLHttpRequest, поскольку нам не требуется проходить по узлам.
Недостаток описанной технологии заключается в том, что мы должны динамически обработать данные и создать таблицу (либо на стороне сервера, либо на стороне клиента). Если в будущем потребуется изменить формат таблицы, то в зависимости от сложности таблицы это может оказаться проблематичным. Добавление или удаление столбца может представлять проблему, поскольку мы должны будем изменить код внутри цикла. Кроме того, следует учесть, что в нашей строке содержатся кавычки; необходимо убедиться, что при создании строки они были представлены правильными управляющими последовательностями. Кроме того, если мы введем JavaScript-код в дескриптор HTML, то получим еще больше двойных и одинарных кавычек, с которыми нужно что-то делать, — необходимо проверить, что все дескрипторы правильно отформатированы и закрыты. Единственная возможность сделать все это — изучить текст после создания строки.
Чтобы избежать данных проблем, мы можем использовать XSLT. Применяя инфраструктуру Ajax, можно объединить XSLT-файл с XMLдокументом и отобразить результаты, не прибегая к использованию методов DOM. Если разработчик знает XSLT, но не является асом в использовании JavaScript, данное решение может быть превосходным.
Обсуждая поиск Ajax, следует отметить, что он не требует дообработки на сервере, следовательно, URL страницы не изменяется, чтобы соответствовать результатам поиска. Таким образом, создавая закладку на URL, мы не получим требуемых результатов поиска. В классических приложениях поиска, подобных Google, мы можем легко копировать URL со страницы, сформированной при поиске, вставить его в электронное письмо, и когда получатель щелкнет на такой ссылке, он увидит искомые результаты. Однако при Ajaxпоиске такую возможность необходимо закодировать отдельно. Подробности этого процесса рассмотрены в разделе 12.5.4.
12.2. Код клиентской части сценария
Технология форматирования XML-данных с использованием XSLT довольно популярна, поскольку XML-файл обладает структурой, легко поддающейся обработке. В предыдущих проектах (например, в приложении опережающего ввода, рассмотренном в главе 10) мы использовали JavaScript, XML и DOM для создания отображаемого HTML-кода. В данном примере для получения того же эффекта мы будем использовать XSLT.
XSLT позволяет форматировать данные, формируя структуру HTML в другом файле и объединяя его с документом XML. Файл XSLT отвечает
486 Часть IV Ajax в примерах
за все, кроме навигации по узлам XML и построения таблиц, меню и HTMLструктуры. Используя Ajax, мы можем извлечь статический или динамический файл XML и статический или динамический файл XLST с сервера, объединив их на стороне клиента для создания HTML-доку мента. Всю работу с XSLT можно выполнить и на стороне сервера, но мы будем рассматривать преобразования на стороне клиента.
12.2.1. Настройка клиента
В данном проекте мы рассмотрим поиск в телефонной книге по имени пользователя. Для этого используется одно текстовое окно и одна кнопка Отправить. Форма поиска показана в листинге 12.1.
Листинг 12.1. Форма клиентской части приложения
<form name="Forml" ID="Forml" onsubmit="GrabNumber();return false;">
// О Добавить обработчик onsubmit
Name: <input name="user" type="text"/> // 0 Вставить текстовое окно
<input type="submit" name="btnSearch" value="Search" />
//© Добавить кнопку отправки <br/Xbr/>
<div id="results"></div>
//О Добавить элемент d i v для результатов </form>
Для инициализации "живого" поиска к дескриптору form необходимо добавить обработчик событий. Обработчик событий onsubmit О перехватывает нажатие клавиши <Enter>, если указатель мыши расположен в текстовом окне, а пользователь щелкает на кнопке Отправить. Данный обработчик событий вызывает функцию GrabNumber(), инициирующую XMLHttpRequest без возврата формы на страницу. (В реальной ситуации необходимо проверить, не отключил ли пользователь JavaScript. В таком случае форма будет отправляться на сервер, и для поддержки подобных пользователей можно использовать классическую форму поиска. Впрочем, в данном проекте мы такую возможность не рассматриваем.)
Созданная нами форма является базовым вариантом, содержащим только обработчик событий, инициирующий XMLHttpRequest. Для сбора пользовательских критериев поиска к форме добавлены текстовое окно © и кнопка Отправить ©. Если мы хотим чего-то необычного, к текстовому окну можно еще добавить обработчик событий onblur, вызывающий функцию GrabNumЬег (); в таком случае поиск будет запущен тогда, когда текстовое окно перестает находиться в фокусе В данном примере активизация поиска связана с обработчиком событий onsubmit.
Затем в документ добавляется элемент div О, в который будут выводиться результаты поиска. Его можно разместить в любом месте станицы. в котором мы желаем видеть результаты. К div добавляется идентифика-

488 Часть IV. Ajax в примерах
ботки, и вызывает сервер, который динамически создаст ответные данные, основываясь на отправленном значении строки запроса. Первым параметром функции LoaciXMLXSLTDoc () является URL страницы РНР, которая генерирует XML-документ, объединенный со строкой запроса, сформированной ссылкой на значений поля HTML-формы О. Второй параметр — это имя XSLTфайла ©, используемого в преобразовании XML-данных. Третий параметр, требуемый функцией LoadXMLXSLTDoc (), представляет собой идентификатор элемента div, в который следует помещать результаты поиска. Идентификатор — это строковое имя выходного элемента, а не ссылка на объект; в данном случае в качестве идентификатора используется строка "results" .
На следующем этапе мы с помощью методов DOM добавляем на Webстраницу изображение-индикатор. Создается элемент изображения © и устанавливается атрибут источника изображения О. Этот созданный элемент добавляется к элементу div с результатами ©. Таким образом, когда наша функция вызывается из обработчика событий onsubmit формы, на страницу помещается файл изображения. Вообще, для пользователя важно создать визуальную обратную связь — сообщение или изображение, — указывающую, что обработка находится в процессе. Благодаря этому пользователь не будет повторно щелкать на кнопке отправки форы, думая, что ничего не происходит (помните, процесс Ajax — "незаметный").
Последний этап — это вызов функции LoadXMLXSLTDoc () ®, инициирующей процесс отправки информации на сервер. Функция LoadXMLXSLTDoc (), описанная в разделе 12.4, обрабатывает вызов объекта ContentLoader (), который запрашивает документы с сервера. Задавая в качестве параметра выходное положение (а не кодируя значение жестко в функции LoadXMLXSLTDoc {)), мы можем многократно использовать данную функцию на одной странице, не требуя для разделения функциональных возможностей добавления множества процедур или операторов if. Следовательно, мы перенаправляем результаты различных поисковых запросов на различные части страницы. Однако, прежде чем мы все это сделаем, давайте посмотрим, как создать на сервере документы XML и XSLT.
12.3.Код серверной части приложения: РНР
Вданном разделе мы создадим для проекта динамический XML-документ, используя РНР — популярный язык подготовки сценариев с открытым исходным кодом (как вы знаете, инфраструктура Ajax совместима с любым серверным языком или платформой). XML-документ генерируется динамически по набору результатов, полученному в ответ на запрос клиента к базе данных. Кроме того, мы покажем, как создать статический документ XSLT, который расположен на сервере и извлекается каждый раз, когда запрашивается динамический файл. Оба указанных документа возвращаются клиенту независимо, когда объект ContentLoader запрашивается в двух отдельных запросах (листинг 12.7). XSLT-код преобразовывает наш динамический XML-документ на стороне клиента и создает HTML-таблицу, которая отображается пользователю.
Глава 12 "Живой" поиск с использованием XSLT 489
12.3.1. Создание XML-документа
Поскольку мы используем XSLT, нам нужен структурированный XMLдокумент, представляющий собой простую запись информации, чтобы XSLфайл мог выполнить стандартное преобразование. В данном проекте мы создадим динамический XML-файл, когда клиент затребует РНР-файл.
Разработка XML-структуры
Прежде чем мы начнем создание XML-файла, необходимо создать шаблон для него. Этот шаблон должен отражать структуру данных, возвращаемых при поиске. В выбранной формулировке задачи (телефонная книга) мы будем возвращать название компании, имя контактного лица, страну и телефонный номер. В листинге 12.3 показан базовый XML-шаблон, содержащий четыре поля.
Листинг 12.3. Базовый XML-файл
<?xml version="l . 0" ?> <phonebook>
<entry>
<company>Company Name</company> <contact>Contact Name</contact> <country>Country Name</country> <phone>Phone Number</phone>
</entry>
</phonebook>
Первым элементом является phonebook. Следующий — элемент entry, содержащий подэлементы со всеми деталями, которые связаны со всеми контактными телефонами, найденными в запросе. Если у нас есть пять результатов, в XML-документе будет пять элементов entry. Имя компании отображается в элементе company. Кроме того, мы добавили имя контактного лица, название страны и номер телефона. Мы не ограничены только указанными полями; в зависимости от того, какую информацию необходимо отобразить, поля можно добавлять и удалять.
Если результаты не найдены, то вместо отображения предупреждающего сообщения можно создать элемент, отображающий эту информацию для пользователя. STO облегчит нам задачу возврата результата пользователю и не потребует дополнительного кода в клиентской части приложения. Код, приведенный в листинге 12.4, практически не отличается от кода из листинга 12.3, но на этот раз мы вводим текст в элементы XML, которые желаем показать пользователю, чтобы сообщить, что результаты не найдены.
Листинг 12.4. Файл XML без результатов
<?xml version="l . 0" ?> <phonebook>
<entry>
<company>No Results</company>
// О Вместо имени компании отображается "No R e s u l t s " <contact>N/A</contact>
490Часть IV. Ajaxв примерах
//© Вместо оставшихся полей отображается "N/A"
<country>N/A</country>
<phone>N/A</phone>
</entry>
</phonebook>
С помощью данного кода мы отображаем пользователю единственную строку, сообщающую, что затребованная информация отсутствует. В дескрипторе company О отображается информация, сообщающая, что результатов нет. В других дескрипторах 0 пользователю сообщается, что информации нет. Если мы не желаем отображать текст "N/A" ("не доступно"), можно добавить вместо него неразрывный пробел, , что позволит показать ячейки таблицы. Если бы мы не добавили вообще никакой информации, ячейки в таблице не появились бы.
Как видите, формат XML имеет очень простую структуру. Если бы данный XML-файл был статическим, пользователю было бы относительно просто добавить в файл нового абонента. Поскольку же он создается динамически, нам потребуется цикл, создающий XML-документ по набору результатов.
Создание динамического XML-документа
Как всегда, мы создаем XML-документ на сервере. Придерживаясь политики использования в примерах различных серверных языков, серверный код дайной главы написан на РНР. Еще раз напомним, что инфраструктуру Ajax можно создать с помощью любого серверного языка, и мы будем описывать только сам принцип реализации серверного кода, не вдаваясь в детали. Итак, в листинге 12.5 показан код серверной части приложения. Код получает параметр строки запроса и генерирует множество результатов запроса базы данных. Затем мы проходим по множеству результатов, создавая в XML-файле согласно приведенному в листинге 12.4 шаблону элемент для каждого номера телефона, полученного в ответ на запрос.
Листинг 12.5. Сценарий phoneXML.php: генерация XML-документа на сервере
<?php
//О Объявить тип MIME header("Content-type: text/xml"); echo("<?xml version='1.01 ?>\n");
//© Соединиться с базой данных
$db = mysql__connect ( "localhost", "ajax", "action"); rnysql_select_db("ajax",$db);
$result = mysql_query("SELECT *
FROM Contacts WHERE ContactName like '%". // © Заполнить запрос
$_GET['q'] ."%"',$db); ?> <phonebook>
<?
// О Проверить результаты
if ($myrow = mysgl_fetch_array($result)) { do {
// © Пройти по множеству результатов