Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Ajax в действии

.pdf
Скачиваний:
92
Добавлен:
01.05.2014
Размер:
6.34 Mб
Скачать

среду, мы также должны проверить возможность атаки через внедрение S Q e кода. Это защитит базу данных от злоумышленных запросов.

Получив значение выбранной области, необходимо сгенерировать строку SQL, чтобы мы могли извлечь из базы данных соответствующие территории ©. Нас интересуют два столбца: TerritoryDescription и TerritorylD из таблицы Territories базы данных. Значение области мы вводим в условие WHERE выражения SQL. Чтобы гарантировать, что в создаваемом списке результаты будут идти в алфавитном порядке, мы дополнительно устанавливаем, условие ORDER BY равным TerritoryDescription. Далее следует выполнить выражение SQL О. В данном случае мы вызываем функцию FillDataTable() создающую соединение с сервером базы данных, выполняющую запрос и возвращающую результаты в таблицу данных.

Итак, мы получили результаты запроса SQL, теперь требуется создать первую часть документа XML ©, который обсуждался в листинге 9.2. Мы начинаем документ и добавляем объект selectElement, содержащий значения параметров formName и formElem, полученные из параметров запроса.

В этом месте нужна проверка: вернул ли запрос SQL какие-либо результаты ©. Если результаты есть, мы добавляем к XML-документу вводную опцию "Select A Territory" ©.

Далее мы циклически проходим по результатам, представленным в DataTable ©, заполняя значением столбца TerritoryDescription дескриптор орtionText. а значением столбца TerritorylD — дескриптор optionValue. Вложив пары "описание-идентификатор" в дескриптор позиции, мы получаем легкий способ циклического прохода значений на стороне клиента с помощью методов DOM XML (реализованных на JavaScript). Заполнив полученными результатами документ XML, нужно закрыть корневой элемент selectChoice и записать ответ на выходной странице 0. Клиенту возвращается ответный документ XML, а объекту ContentLoader сообщается, что обработка на стороне сервера завершена. Далее объект ContentLoader вызывает на стороне клиента функцию FillDropDown(), которая обработает созданный нами XML-документ.

Подытожим, что мы сделали на стороне сервера. Мы взяли значение из выбранного элемента списка и организовали запрос к базе данных, не отправляя на дообработку на сервере всю страницу. Затем мы сгенерировали документ XML и вернули его клиенту. Следующий этап обработки снова будет происходить на стороне клиента, где мы должны преобразовать элементы XML в опции второго списка.

9.4. Представление результатов

Итак, теперь в документе XML у нас есть результаты запроса базы данных, и мы собираемся пройти по его элементам, используя программный интерфейс приложения DOM JavaScript. С помощью функции getElementsByTag^' Name {) мы можем легко "перепрыгивать" на любой элемент документа. Эта Функция с помощью имени элемента находит его в DOM (немного похоже иа то, как выдвигались алфавитные закладки в старомодном Rolodex). По-

Глава 9. Динамические связанные комбинации

дольку многие элементы в документе XML могут иметь одинаковые име- а функция getElementsByTagName () в действительности возвращает массив цементов, выстроенных по порядку их появления в документе.

д.4.1. Навигация в документе XML

Наконец-то мы можем завершить клиентскую часть сценария, добавляющую опции во второй список. Имена формы и элемента select, которые мы должны заполнить, задаются в документе XML вместе со всеми доступными опциями списка. Чтобы выделить эти опции и вставить их в элемент select, нам необходимо пройти по всем элементам документа.

Как только объект ContentLoader получит с сервера документ XML, он вызывает функцию FillDropDown(), приведенную в листинге 9.2. В функции FillDropDown() мы проходим по элементам entry документа XML и создаем для каждого из них новый объект Option. Данные объекты представляют пары "текст-значение"', которые будут добавлены во второй список. В листинге 9.5 функция FillDropDown() показана полностью.

Листинг 9.5. Обновление страницы с использованием данных из XML-ответа

/ / О Получить ответный XML-документ function FillDropDown(){

// © Получить имя формы и элемента select

var xmlDoc = this.req.responseXML.documentElement; var xSel = xmlDoc.

getElementsByTagName('selectElement1)[01; var strFName = xSel.

childNodes[0],firstChild.nodeValue; var strEName = xSel.

childNodes[1].firstChild.nodeValue; // © Получить ссылку на элемент select

var objDDL = document.forms[strFName]. elements[strEName];

objDDL.options.length = 0;

// О Последовательно пройти по XML-документу, добавляя опции var xRows = xmlDoc.

getElementsByTagName('entry1 ); for{i=0;i<xRows.length;i++){

var theText = xRows[i]. childNodes[0].firstChild.nodeValue;

var theValue = xRowsfi]. childNodes[l].firstChild.nodeValue;

var option - new Option(theText, theValue); try{ objDDL.add(option,null);

 

}catch

(e){

 

objDDL. add{option, - I);

}

 

 

}

 

 

_ J

 

 

J i

362 Часть IV. Ajax в примерах

Функция FillDropDown() вызывается объектом ContentLoader. как толь. ко он получит и разберет XML-ответ. Объект ContentLoader вызывается в функции FillDropDown() посредством ссылки this, и с его помощью у», получаем ответный документ responseXML Как только у нас будет ссылка на объект documentElement отклика О, мы можем использовать функции DOM JavaScript для навигации по его узлам. Первая информация, которую нам нужно получить, — целевой список select, к которому мы добавим новые опции. Мы ищем элемент, именуемый selectElement, используя функцию getElementsByTagName () и выбирая первый элемент из возвращенного этой функцией массива. После этого мы можем переходить к дочерним узлам @. Первый дочерний узел содержит имя формы, а второй — имя списка select.

Используя два указанных значения, мы обращаемся собственно к самому целевому списку © и очищаем все существующие опции, устанавливая длину массива его опций равной 0. Теперь мы можем добавлять новые опции к списку. Нам требуется доступ к элементам entry документа XML, поэтому мы еще раз вызываем функцию getElementsByTagName(). На этот раз требуется циклически пройти по массиву элементов, который возвращает эта функция, и получить пары "текст-значение" из каждого элемента О. Первый узел-потомок каждого элемента entry представляет собой текст опции, который будет показан пользователю, второй — значение. Получив два этих значения, мы создаем новый объект Option, передающий текст опции как первый параметр конструктора и значение опции — как второй. Затем к целевому элементу target добавляется новая опция, и процесс повторяется, пока не будут добавлены все новые опции. Сигнатура метода для select.add() зависит от браузера, поэтому мы используем оператор try .. . catch, пока не получим искомые сведения. Это все, что нужно-было сделать для завершения нашей структуры связанных списков. Теперь можно загружать HTMLстраницу, выбирать регион и наблюдать, как второй раскрывающийся список заполняется прямо из базы данных. На рис. 9.7 показано, как это выглядит. В данном примере из первого списка выбран регион Eastern, затем из базы данных извлекаются и отображаются во втором списке соответствующие территории. Далее из первого списка выбирается регион Southern, после чего второй список заполняется соответствующими территориями.

Как видно на рис. 9.7, нам осталось сделать еще одно: изменить внешний вид списка, сделав его более привлекательным. Размер второго списка увеличивается при заполнении опциями. Данное смещение размера можно исправить, применив к элементу правило CSS (Cascading Style Sheet — каскадная таблица стилей).

9.4.2. Применение каскадных таблиц стилей

Каскадные таблицы стилей позволяют менять визуальные свойства элемента. Мы можем изменить цвет и семейство шрифта, ширину элемента и т.д. На рис. 9.7 видно, что второй элемент select изначально имеет ширину всего несколько пикселей, поскольку не содержит ни одной опции. При выборе из первого списка региона Eastern второй элемент select расширяется Это

Рис. 9,7. Зависимые списки в действии

Рис. 9.8. В различных браузерах элемент s e l e c t визуализируется по-разному

изменение размера режет глаза и может раздражать пользователя. Чтобы решить эту проблему, можно задать ширину списка.

<select name="ddlTerritory" style="width:200px"x/select>

Однако так мы не снимем всех проблем, если, например, одно из отображенных значений больше установленной нами ширины. В Firefox, если элемент находится в фокусе, опции раскрывающегося списка показываются так, чтобы был виден весь их текст. Однако в Microsoft Internet Explorer текст усекается и не виден пользователю, как показано на рис 9.8.

Чтобы избежать проблем, связанных с Internet Explorer, необходимо установить ширину списка равной ширине наибольшей опции. В большинстве случаев единственной возможностью определения требуемого числа пикселей является метод проб и ошибок

Некоторые разработчики используют специальные уловки CSS только для того, чтобы установить в Internet Explorer большую ширину элемента: 1

style="width:100px;_width:250px"

Internet Explorer распознает ширину, начинающуюся со знака подчеркивания, тогда как другие браузеры ее игнорируют. Следовательно, рамки выбора Internet Explorer будут иметь ширину 250 пикселей, тогда как другие браузеры — 100 пикселей. Однако полагаться на подобные "специфические особенности" браузера не стоит, поскольку в будущем их, возможно, исправят, а это нарушит отображение ваших страниц.

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

9.5. Дополнительные вопросы

В данной главе мы создали упрощенную версию сценария зависимого выбора. Серверу отправлялся единственный параметр, в ответ возвращался набор результатов для одного выбранного пункта. Возможно, вам кажется, что приложение должно работать иначе. Допустим, было бы неплохо добавить к форме еще один элемент, чтобы получилась тройная зависимость. Возможно, вы хотите, чтобы пользователь мог выбирать несколько элементов в первом списке. Если это так, тогда вам стоит прочесть следующие разделы, в которых рассказано, как реализовать названные идеи.

9.5.1. Запросы при выборе нескольких элементов

Обсуждавшийся выше код является очень простым,-он позволяет пользователю выбирать только одну опцию из каждого списка. В некоторых случаях пользователю требуется выбрать несколько опций в первом списке. Это означает, что второй список в нашей комбинации будет заполняться значениями, соответствующими каждой выбранной опции первого списка. Этого можно добиться, несколько модифицировав серверную и клиентскую часть нашего приложения.

Первое, что требуется сделать, — это настроить первый список так, чтобы разрешить выбор нескольких позиций. Для этого к дескриптору select нужно добавить атрибут multiple. Чтобы задать количество отображаемых опций, можно добавить атрибут size. Если значение size меньше числа опций, список будет прокручиваемым (и можно будет выбирать элементы, которые не видны в текущий момент).

<select name="ddlRegion" multiple size="4"

onchange="FillTerritory(this,document.Forml.ddlTerritory)"> <option value="l">Eastern</option>

<option value=:"2">We3tern</option> <option value="3">Northern</option>

<option value="4">Southern</option> </select>

WHERE

Далее необходимо изменить функцию FillTerritory<). Вместо того чтобы просто обращаться к выбранному номеру элемента select, нам требуется последовательно пройти по всем опциям и найти все выбранные значения. Затем значения всех выбранных опций добавляются в строку параметров.

function FillTerritory(oElem,oTarget){ var url = 'DoubleComboMultiple.aspx1;

var strParams — "f=" + oTarget.form.name + "&e=" + oTarget.name; for(var i=0;KoElem.options.length;i++)[

if(oElem.options[i].selected){

strParams += "&q=" + oElem.options[i].value;

}

}

var loaderl = new net.ContentLoader(url,FillDropDown,null,"POST",strParams);

}

Последнее, что требуется сделать, — это изменить код серверной части сценария, реализовав обработку нескольких значений, переданных в запрос. В .NET несколько значений, разделенныхзапятыми, представляются водной строке. Чтобы получить отдельные элементы, необходимо расщепить строку на массив. Затем с помощью циклического прохода массивазадается условие

для оператора SQL.

Dim strQuery As String - Request.Form("q") Dim strWhere As String = ""

Dim arrayStrt) As String = strQuery.Split(",") Dim i As Integer

For Each i In arrayStr

If strWhere.Length > 0 Then strWhere - strWhere & " OR "

End If

strWhere = strWhere & " regionid = " & i Next

Dim strSql As String = "SELECT " & _

"TerritoryDescription, " & _

"TerritorylD" & __

"FROM Territories" & _

"WHERE " & strWhere & _

"ORDER ВУ TerritoryDescription"

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

9.5.2. Переход от двойного связного выбора к тройному

Переход от двойного связного списка к тройному требует минимальных изменений (в зависимости от того, насколько мы хотим изменить логику сервера). Первый вариант заключается в том, чтобы перенести нашу логику на несколько серверных страниц и на каждой из них запустить свой запрос. Такое решение означает добавление к каждому обработчику onchange нужного списка еще одного параметра, представляющего URL вызываемого серверного сценария.

Возможен и другой вариант: добавить к серверному коду оператор £jfl else или switch-case. Чтобы использовать структуру if-else, нужно опр^ делить, какой запрос следует выполнять для возврата соответствующих зн&.

чений. Простейшая проверка: решить, какой запрос SQL использовать, ос. новываясь на имени заполняемого элемента select. Таким образом, пр> реализации тройной комбинации мы можем проверять значение переменной strElem. При этом нам не потребуется изменять обработчики событий onchange в клиентской части кода.

Dim

strSql

As

String

If

strElem -

"ddlTerritory" Then

 

strSql

=

"SELECT TerritoryDescription, " $ _

 

 

" TerritorylD" & __

 

 

" FROM Territories" & _

 

 

• WHERE " & strWhere & _

 

 

"

ORDER BY TerritoryDescription"

Else

 

 

 

strSql = "SELECT Columnl, Column2" &

 

 

" FROM TableName" & __

 

 

" WHERE " & strWhere &

 

 

"

ORDER ВУ Column2"

End

If

 

 

При таком решении (и при условии, что раскрывающиеся списки заполнены уникальными именами) на странице можно будет использовать множественные связанные списки, не разделяя логику на различные серверные страницы.

9.6. Реструктуризация

_ ^ _

 

 

 

Как вы думаете, чего не хватает в изложенном материале? Обобщения! Мы описали отличную модную технику реализации двойных списков, которая, однако, требует небольшой доводки, чтобы ее можно было обобщить. Мы перейдем к этому, так что держитесь! Но вначале давайте рассмотрим кое-что еще более фундаментальное: инкапсуляцию некоторых элементов внутренней "кухни" Ajax. Для начала можно взять объект net.ContentLoader, кратко представленный в главе 3 и более подробно рассмотренный в главе 5. Давайте создадим этот объект, чтобы еще лучше понять Ajax. В идеальном случае данный элемент должен стать нашим объектом-'тгомощником" Ajax, который формирует всю инфраструктуру Ajax. Это позволит сосредоточиться на разработке вопросов, связанных только с зависимыми списками, а также сократить код, требуемый всеми остальными компонентами. Наш улучшенный объект net.ContentLoader в идеальном случае должен инкапсулировать состояние и поведение, требуемые для выполнения перечисленных ниже задач.

• Создание объекта XMLHttpRequest, который был бы понятен всем браузерам и не зависел от отправки запросов. Это позволит вызывающей стороне использовать метод создания независимо от остальной части объекта. Данная возможность полезна, если вызывающая сторона использует другую идиому, каркас или механизм для действия запроса/отклика.

• Предложить более удобный API для обработки параметров запроса. В идеальном случае вызывающая сторона должна просто передавать от приложения состояние и разрешать "помощнику" net. ContentLoader решать все вопросы, связанные с созданием строк запроса.

• Маршрутизация отклика обратно к компоненту, который знает, как его обработать, и выполнение соответствующей обработки ошибок.

Ну что ж, начнем сборку объекта net .ContentLoader, а затем переформулируем наш сценарий двойного списка как компонент.

9.6.1. Новый и улучшенный объект netContentLoader

Рассмотрим вначале, как следует изменить конструктор.

net.ContentLoader = function! component, url, method, requestParams ) {

// Состояние net.ContentLoader this.component = component; this.url = url;

this.requestParams = requestParams; this.method = method;

}

Данный конструктор вызывается с четырьмя аргументами. Первый, component, обозначает объект, пользующийся услугами данного "помощника". Вспомогательный объект будет предполагать, что component имеет метод aj axUpdate () для обработки откликов и метод handleError () для обработки состояний ошибки. Подробнее этот вопрос мы рассмотрим позже. Второй аргумент, url, обозначает URL, вызываемый данным "помощником" для асинхронного получения данных с сервера. Параметр method обозначает метод HTTP-запроса. Допускаются значения GET и POST. Наконец, аргумент геquestParameters представляет собой массив строк вида key=value, которые обозначают параметры запроса, передаваемые запросу. Это позволяет вызывающей стороне задавать набор параметров запроса, которые не меняются между запросами. Эти параметры будут присоединены к дополнительным параметрам запроса, передаваемым в рассмотренный ниже метод sendRequest Таким образом, наш объект-'помощник" может следующим образом созда ваться клиентом:

var str = "Eastern";

var aComp - new SomeCoolComponent(...);

var ajaxHelper = new net.ContentLoader( aComp, "getRefreshData.aspx", "POST",

[ "query=" + str, "ignore_case=true" ]

);

Рассмотрим теперь оставшуюся часть API. Здесь следует обратить в: ше внимание на стилистическую природу примера кода. Область действ» методов этого объекта распространяется на объект-прототип, прикрепле; ный к функции конструктора. Данная технология является стандартной п\ написании объектно-ориентированного кода JavaScript, поскольку примен

ет определения метода ко всем экземплярам объекта. Тем не менее сущ^. ствует несколько способов, позволяющих задать такое поведение синтаксически. Одним из наших любимых (его структура позаимствована из библиотеки prototype.js, внедренной в Ruby On Rails) является буквальное создание объекта-прототипа.

net.ContentLoader.prototype = {

// Первый метод, прикрепленный к прототипу m e t h o d l : f u n c t i o n ( a , b, с) {

ь

// Второй метод

method2: function() { }, method3: function(a) { }

};

С точки зрения синтаксиса достоинством данного подхода является лаконичность. Как читать данный фрагмент? Внешние скобки представляют литерал объекта, а содержимое — это разделенный запятыми список пар "свойство-значение" в объекте. В данном случае наши свойства являются методами. Пары "свойство-значение" задаются как имя свойства, затем следует двоеточие, а после него ~ значение свойства. В этом случае значения (или определения, если вам угодно) являются литералами функций. Все просто, правда? Достаточно помнить, что методы, которые будут использоваться начиная с этого момента, предполагаются содержащимися внутри литеэала объекта-прототипа (как показано выше). Кроме того, обратите внимание на то, что последнее свойство не требует (а следовательно, может не №геть) после себя запятой. Теперь давайте вернемся к рассматриваемой задаче: сборке API.

API должен удовлетворять упомянутым выше требованиям, поэтому рас- :мотрим их последовательно. Первое, что нам требуется, — обработка содания объекта XMLHttpRequest независимо от браузера. Звучит, как метод! С счастью, мы уже реализовывали его несколько раз. Все, что требуется сейгас, — создать его как метод нашего "помощника" (листинг 9.6), чтобы нам ольше никогда не приходилось его писать.

Листинг 9.6. Метод getTransport

getTransport:

function() {

 

var

transport;

 

//

Родной

объект

 

 

if {

window.XMLHttpRequest )

 

t r a n s p o r t

= new XMLHttpRequest();

//

Объект

IE

ActiveX

 

 

e l s e

if

{ window.ActiveXObject ) {

 

t r y {

t r a n s p o r t =

new ActiveXObject(r Msxml2.XMLHTTP') ; }

 

c a t c h ( e r r ) {

 

 

 

t r a n s p o r t = new

ActiveXObject('Microsoft.XMLHTTP1 );

 

}

 

 

 

 

 

}

 

 

 

 

r e t u r n t r a n s p o r t ;

},

Данный код не требует очень подробных объяснений, поскольку данную тему мы рассматривали неоднократно, однако на этот раз мы привели аккуратно упакованный метод, обеспечивающий работающий во всех браузерах объект Ajax транспорта данных для обработки нашей асинхронной связи. Второе упомянутое нами требование касалось обеспечения более удобного API для обработки параметров запроса. Чтобы его можно было использовать во множестве приложений, очевидно, что отправляемый запрос будет требовать в качестве параметров значения времени выполнения. Мы уже записали некоторое исходное состояние, представляющее параметры запроса, постоянные для различных запросов, но кроме этого нам требуются значения времени выполнения. Рассмотрим поддержку приведенного ниже кода.

//Предполагается инициализация со значениями времени выполнения

var a,b,c;

var ajaxHelper = new net . ContentLoader( ... ); ajaxHelper.sendRequest( "paraml=" + a, "param2=" + b,

"param3=" + с );

Итак, при данном требовании к использованию определяется sendRequest, как показано в листинге 9.7.

sendRequest: function() {

/ / О Записываем аргументы в массив var requestParams = ( ] ;

for ( var i = 0 ; i < arguments.length ; i++ ) { requestParams.push(arguments[i]);

}

// © Создаем запрос

var request = this.getTransport(); request.open( this.method, this.url, true ); request.setRequestHeader( 'Content-Type',

'application/x-www-form-urlencoded'); // © Задается обратный вызов

var oThis = this;

 

 

request.onreadystatechange = function()

{

 

 

oThis.handleAjaxResponse(request) } ;

// О Отправляется запрос

 

 

request.send( this.queryString(requestParams) );

>-

 

 

Данный метод расщепляет процесс отправки запроса на четыре этапа. Рассмотрим эти этапы подробнее.

ОДанный этап использует тот факт, что JavaScript создает псевдомассив arguments, область действия которого распространяется на функцию. Как можно догадаться по имени, агдитег^эсодержит аргументы, переданные функции. В данном случае ожидается, что аргументы - это строки вида key=value. Пока что мы просто копируем их в массив первого класса. Кроме того, обратите внимание, что все переменные, созданные в этом методе, начинаются с ключевого слова var. Хотя компилятору