
Ajax в действии
.pdf
350 Часть IV Ajax в примерах
Если вы когда-либо покупали рубашку в интерактивном магазине, то мог-Я ли столкнуться со следующей проблемой. Вы выбираете размер рубашки из раскрывающегося списка, а из следующего списка выбираете ее цвет. Затем вы отправляете заполненную форму и получаете сообщение 'Товара нет на складе", написанное гигантскими буквами. С чувством глубокого разочарования вы щелкаете на кнопке Назад или на предложенной ссылке, а затем выбираете новый цвет.
Используя Ajax, подобного срыва планов можно избежать. Например списки выбора можно связать так, чтобы, когда пользователь выберет размер из первого списка, во второй список непосредственно из базы данных выводились все имеющиеся в наличии расцветки рубашек данного размера (при этом от пользователя не требуется обновлять страницу). Ранее связывание нескольких списков выбора для выполнения указанной задачи требовало интенсивного кодирования на JavaScript с использованием массивов или до-1 полнительной обработки на сервере, но теперь есть лучший способ, который предлагает Ajax.
9.1. Сценарий двойной комбинации
В двойных связных списках содержимое одного списка зависит от варианта, выбранного в другом списке. Когда пользователь выбирает значение из первого списка, все элементы второго динамически обновляются. Обычно подобная схема называется сценарием двойной комбинации (double-combo script). \ Существуют два традиционных решения проблемы динамического заполнения второго списка: одно реализуется на стороне клиента, второе — на сервере. Напомним их, чтобы понять принцип их действия и вопросы, свя-
занные с ними.
9.1,1. Недостатки клиентского решения
Традиционно первое, о чем думают разработчики, — это использовать решение, переносящее всю работу на сторону клиента. В подобном решении, основанном на JavaScript, значения списка жестко кодируются на Web-странице в массивах JavaScript. После того как вы выбрали размер рубашки, сценарий заполняет следующий список, выбирая значения из массива. Данное решение показано на рис. 9.1.
С данным клиентским методом связана одна проблема: поскольку пользователь не связывается с сервером, на момент первого выбора массив может оказаться устаревшим. Другую проблему составляет время загрузки исходной страницы, на которое очень сильно влияет количество позиций в обоих списках. Представьте себе магазин с тысячей предметов; в массив JavaScript необходимо поместить цену каждой вещи. Поскольку код, представляющий данный массив, будет частью страницы, пользователю придется довольно долго ждать первой загрузки сайта (пользователь никак не может заблаговременно получить данную информацию). С другой стороны, метод JavaScript имеет и одно преимущество: после первой загрузки все происхо-

рис. 9.1. Клиентское решение
Рис. 9.2. Метод дообработки на сервере
дит достаточно быстро. Никакой заметной задержки между выбором позиции первого списка и появлением второго не наблюдается. Таким образом, данный метод подходит только в том случае, если требуется реализовать лишь несколько связных списков, которые не очень сильно повлияют на время загрузки страницы.
9.1.2. Недостатки клиентского решения
Следующее традиционное решение заключается в возврате формы на сервер Для дообработки. В данном методе обработчик событий onchange в первом списке инициирует возврат страницы на сервер с помощью метода submit (), введенного в JavaScript-представление формы. В результате страница отправляется на сервер, донося выбранный пользователем элемент первого списка. Сервер, в свою очередь, запрашивает базу данных согласно значению, которое выбрал пользователь, и динамически заполняет второй список новыми значениями, вызывая повторное отображение страницы. Действия данного серверного метода представлены на рис. 9.2.
Недостатком описанного серверного метода является число обращений к серверу; при каждой перезагрузке страницы наблюдается задержка, вызванная необходимостью копирования по сети всей страницы. Визуально данная структура выглядит так, как показано на рис. 9.2. Кроме того, на стороне сервера необходимо реализовать дополнительный код, отвечающий за

Рис. 9.3. Решение, предлагаемое Ajax

Таблица 9.1. Три способа заполнения элемента формы |
|
|
|
|||||
Метод |
|
Преимущества |
Недостатки |
|||||
Жестко закодировать значения |
|
Не требует обработки на |
Опции не могут быть |
|||||
в элементе s e l e c t |
|
стороне сервера |
динамическими |
|||||
|
|
|
|
|
|
|
|
|
Заполнить значения, используя |
|
|
|
|
""" |
|||
сценарий серверной части |
Опции могут быть |
Требует дополнительной |
||||||
приложения |
динамическими и |
обработки на сервере |
||||||
|
|
|
заполняться из базы |
|
|
|
||
|
|
|
данных |
|
|
|
|
Использовать Ajax для
заполнения формы значениями; Можно связать с другими Требует дополнительной
для извлечения значений |
значениями на странице обработки на сервере |
требуется дообработка на |
|
сервере |
|
Как показано в табл. 9.1, первый элемент формы можно заполнить тремя различными способами.
Первый метод — жестко закодировать значения в элементе select. Данный метод хорош, когда вы используете несколько опций, которые не будут меняться. Второй метод — заполнить значения, используя сценарий серверной части приложения. При таком подходе опции заполняются при визуализации страницы, что позволяет извлекать их из базы данных или файла XML. Третий метод — использовать для заполнения значений инфраструктуру Ajax; этот метод требует дообработки на сервере (извлечения значений), но не требует повторной визуализации всей страницы.
В данном примере мы жестко закодируем значения в списке, поскольку используется всего четыре опции, которые не являются динамическими. Наиболее удачным решением задачи динамической загрузки значений в первый список является использование серверного сценария, заполняющего список в процессе визуализации страницу. В данном случае для заполнения первого списка Ajax использовать не стоит (если только содержимое списка не зависит от других значений, выбранных пользователем в форме).
Как показано в листинге 9.1, первый список должен содержать обработчик событий onchange, добавленный к элементу select. Этот обработчик событий вызывает функцию FillTerritoryO (JavaScript), которая инициирует процесс заполнения второго списка, отправляя запрос серверу.
Листинг 9.1. Форма со связными списками Н Н Н Н Н И Н В И Н Н
<form name="Forml"> <select name="ddlRegion" |
|
||
|
onchange="FillTerritory(this,document.Forml.ddlTerritory)"> |
|
|
|
<option value="-l">Pick A Region</option> |
|
|
|
<option |
value="l">Eastern</option> |
|
|
<option |
value="2">Western</option> |
|
|
<option |
value-"3">Northern</option> |
|
|
<option |
value="4">Southern</option> |
|
|
</select> |
<select name="ddlTerritory"></select> </form> |
^ |
Глава 9. Динамические связанные комбинации |
355 |
Код, приведенный в листинге 9.1, создает форму, инициирующую процесс fillTerritory{) при выборе элемента из первого списка. Функции FillTer- i-itoryO мы передаем две объектные ссылки на элемент. Первая из них — объект списка, к которому прикреплен обработчик событий, вторая — список, который требуется заполнить. Далее нам необходимо разработать код серверной части приложения для FillTerritory (), который бы отправлял я аш запрос серверу.
9.2.2. Разработка взаимодействия клиент/сервер
Основной целью функции FillTerritory () является сбор информации, необходимой для отправки запроса серверу. Эта информация включает выбранную опцию из первого списка, имя формы и имя второго списка. Имея эти данные, мы можем использовать функции Ajax в нашей библиотеке JavaScript для отправки запроса серверу. Итак, первое, что требуется сделать, — это реализовать функциональные возможности Ajax. Код, требуемый для связи с внешним файлом JavaScript net. j s (определяет объект ContentLoader), тривиален. Все, что требуется, — добавить приведенную ниже строку между дескрипторами заголовка вашего документа HTML.
<script type="text/javascript" src="net.js"x/script>
Объект ContentLoader выполняет всю работу, определяя, как отправить запрос серверу, скрывая при этом весь код (зависящий от используемого браузера) в удобном интерфейсном объекте, который мы ввели в главе 3. Это позволяет обмениваться данными с сервером, не обновляя страницу.
Добавив функциональные возможности Ajax, мы сможем построить функцию FillTerritoryO, показанную в листинге 9.2; данный код также включается в заголовок нашего документа.
Листинг 9.2. Функция F i l l T e r r i t o r y U инициализирует запрос Ajax
<script type="text/javascript"> function FillTerritory(oElem,©Target){
// О Получить значение, выбранное в списке var strValue = oElem.options[
oElem.selectedlndex].value; // © Установить целевой URL
var url = "DoubleComboXML.aspx"; // © Построить строку параметров
var strParams = "q=" + strValue + "&f=" + oTarget.form.name +
"&e=" + oTarget.name;
// О Инициализировать загрузчик содержимого var loaderl = new net.ContentLoader(url,FillDropDown,null,
"POST",strParams);

Рис. 9.6. Поток процесса на стороне сервера
Функция FillTerritory () принимает два параметра, в данном случае передаваемые от обработчика событий onchange из первого списка. Эти параметры — ссылки на первый и второй элементы select О. Мы обращаемся к значению, которое пользователь выбрал в первом списке ®, и устанавливаем URL целевого серверного сценария ©. Далее мы создаем параметры, которые будут отправлены серверу: формируем строку с таким же синтаксисом, как и строка запроса, используя амперсанд для разделения пар "имя-значение". В данном примере мы отправляем значение, представляющее выбранную область, как q, имя формы —- как f, а имя второго элемента select — как е. Серверная часть кода использует значение выбранной области для запроса базы данных и отправит имена формы и элемента select клиенту в ответном документе XML. С помощью этой информации клиент определит, какую форму и элемент управления обновить. Создав строку параметров, остается только инициировать процесс Ajax.
Чтобы начать процесс О, мы вызываем конструктор ContentLoaderO и передаем целевой URL, функцию, вызываемую при получении отклика сервера, функцию обработки ошибок, используемый метод HTTP и отправляемые параметры. В данном случае при возврате данных с сервера будет вызываться функция FiliDropDown (), в качестве функции обработки ошибок устанавливается функция, по умолчанию принятая ContentLoader, кроме того, мы используем запрос POST.
В это время ContentLoader ожидает возврата от сервера документа XMI* Мы продолжим разбор клиентской части кода в разделе 9.4, а пока изучим, что должен сейчас сделать сервер.
9.3. Реализация сервера: VB.NET
В коде серверной части приложения необходимо реализовать извлечение территорий, принадлежащих к выбранной клиентом области, из базы данных и возврат их клиенту в документе XML. Согласно набору элементов, полученных из запроса SQL, создается документ XML, который и возвращается клиенту. Поток процесса на стороне сервера показан на рис. 9.6.
Код на стороне сервера активизируется запросом объекта ContentLoader со стороны клиента. Вначале код сервера извлекает значение параметра запроса q, представляющего выбранную область. Согласно значению q создается динамический запрос SQL, который, обращаясь к базе данных, находит
Глава 9 Динамические связанные комбинации |
357 |
пары "текст-значение" для второго раскрывающегося списка. Затем данные, полученные из базы данных в ответ на запрос, форматируются в формате XML и возвращаются клиенту. Прежде чем написать код, выполняющий описанные действия, нам необходимо определить основную структуру документа XML.
9.3.1- Определение формата XML-omeema
Нам требуется создать простой документ XML, возвращающий клиенту результаты нашего запроса к базе данных. Этот документ будет содержать опции, которыми будет заполняться второй список. Для представления каждой опции требуются два элемента: один, содержащий текст опции, второй — ее значение.
Документ XML в нашем примере содержит корневой элемент selectchoice, вмещающий единственный элемент selectElement, за которым следует один или несколько элементов entry. Элемент selectElement содержит имена формы HTML из раскрывающегося списка, который будет заполнен результатами, полученными из базы данных. Каждый элемент entry содержит два дочерних элемента, optionText и optionValue, которые вмещают значения, представляющие описание и идентификатор каждой территории. Описанная структура показана в листинге 9.3.
Листинг 9.3. Пример формата XML-ответа
<?xml version="l.0" ?> <selectChoice>
<selectElement>
<formName>Forml</formName>
<formElem>ddlTerritory</formElem>
</selectElement>
<entry>
<optionText>Select A |
Territory</optionText> |
||
<optionValue>-l</optionValue> |
|||
</entry> |
|
|
|
<entry> |
|
|
|
<optionText>TerritoryDescription</optionText> |
|||
<optionValue>TerritoryID</optionValue> |
|||
</entry> |
|
|
|
< / s e l e c t C h o i c e > |
|
|
м |
Изучая пример документа XML в листинге 9.3, обратите внимание на наличие позиции, содержащей текст опции ''Select A Territory" ("Выберите территорию"). Это первая опция, отображаемая в списке, она предлагает пользователю выбрать значение. Код сервера включает это значение в начало каждого ответного документа до получения динамических опций из базы данных.
Теперь, определив ответный документ, можно писать код, динамически создающий XML-документ и возвращающий его клиенту.
•358 Часть IV. Ajax в примерах
9.3.2. Написание кода сервера
Код VB.NET серверной части приложения достаточно прямолинеен. Мы запрашиваем базу данных, которая возвращает набор записей. После этого мы циклически проходим по набору записей и создаем XML-документ, который затем возвращаем клиенту. Если мы не найдем ни одной записи, тогда элементы entry не создаются и статическая опция "Select A Territory" опускается. Как показано в листинге 9.4, серверная часть кода не очень сложна. Она просто содержит операторы, извлекающие значения формы, отправленные на сервер, устанавливает тип содержимого, выполняет поиск и выдает документ XML.
В приведенном примере использована база данных Northwind из SQL Server (Microsoft).
Листинг 9.4. DoubleConiboXML.aspx. vb: создание XML-ответа на сервере^
//Реализация метода Page_Load Private Sub Page_Load( _
ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load
//О Установить тип содержимого
Response.ContentType = "text/xml" |
|
|
// © Извлечь отправленные данные |
|
|
Dim strQuery As String |
|
|
strQuery • Request.Form("q") |
|
|
Dim strForm As String |
|
|
strForm *= Request.Form("f") |
\ |
_ / |
Dim strElem As String |
|
|
strElem = Request.Form("e") |
|
|
// © Создать утверждение SQL |
|
|
Dim strSql As String = "SELECT " & |
|
|
"TerritoryDescription, |
" |
& _ |
"TerritorylD" & _ |
|
|
"FROM Territories" & __
"WHERE regionid = " & _ strQuery & " ORDER BY " & _ "TerritoryDescription"
Dim dtOptions As DataTable
//О Выполнить утверждение SQL dtOptions = FillDataTable(strSql)
//© Начать документ XML
Dim strXML As StringBuilder
strXML = New StringBuilder("<?xml " & _ "version=""l,0"" ?>")
strXML.Append{"<selectChoice>")
strXML.Append("<selectElement>") strXML.Append("<formName>" & _
strForm & _ "</formName>")
strXML.Append("<formElem>" & _ strElem & "</formElem>")
strXML.Append("</selectElement>")
Глава 9. Динамическиесвязанныекомбинации |
359 |
// О Проверить наличие результатов If dtOptions.Rows.Count > 0 Then
// в Добавить первый элемент выбора strXML.Append("<entry>") strXML.Append{"<optionText>" & _
"Select A Territory" & _ "</optionText>")
strXML.Append("<optionValue>-l" & _ "</optionValue>")
strXML.Append("</entry>")
// © Циклически пройти по набору результатов и добавить элементы XML
Dim row As DataRow |
|
|
For Each row In dtOptions.Rows |
|
|
strXML.Append("<entry>") |
|
|
strXML.Append("<optionText:>" & __ |
|
|
row("TerritoryDescription") |
& _ |
|
"</optionText>") |
|
|
strXML.Append("<optionValue>" & _ |
|
|
row("TerritoryID") |
& _ |
|
"</optionValue>") |
|
|
strXML. Append. ("</entry>") |
|
|
Next |
|
|
End If |
|
|
// © Вернуть документ XML |
|
|
strXML.Append("</selectChoice>") |
|
|
Response.Write(strXML.ToString) |
|
|
End Sub |
|
|
Public Function FillDataTable( _ |
|
|
ByVal sqlQuery As String) ___ |
||
As DataTable |
|
|
Dim strConn As String = _ |
|
|
"Initial Catalog - Northwind; |
" fi _ |
|
"Data Source=127.0.0.1; " & _ |
|
|
"Integrated Security=true;" |
|
|
Dim cmdl As _ |
|
|
New SqlClient.SqlDataAdapter{sqlQuery, |
_ |
|
strConn) |
|
|
Dim dataSetl As New DataSet |
|
|
cmdl . Fill(dataSetl) |
|
|
cmdl.Dispose() |
|
|
Return dataSetl.Tables(O) |
|
|
End F u n c t i o n |
|
_ _ • |
Установив тип содержимого страницы О равным text/xml, мы гарантируем, что XMLHttpRequest правильно разберет отклик сервера на стороне клиента.
Из параметров запроса ©, полученных от клиента, мы получаем значение выбранной области, имя формы HTML и имя элемента. Чтобы лишний раз перестраховаться, мы можем добавить в этом месте проверку, гарантирующую, что данные значения не пустые. Если в ходе проверки не будет найдено хотя бы одно требуемое значение, сценарий может возвращать сообщение об ошибке. Кроме того, прежде чем приложение войдет в производственную