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

Ajax в действии

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

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

JavaScript сильно бы понравилось, если бы мы оставили ключевое слово varB покое, то, что мы так не поступили, очень важно Почему? Если мы опустим это ключевое слово, переменная создается с глобальной областью действия — т.е. видимой для всего кода в вашей вселенной JavaScript! Это может вызвать нежелательное взаимодействие с остальным кодом {например, в использованном вами чьем-либо сценарии переменная названа точно так же, как и у вас). Короче говоря, подобная ситуация гарантирует кошмар отладки. Позаботьтесь о себе сами — приучитесь использовать переменные с локальной областью действия везде, где это возможно.

©Здесь наш метод использует метод getTransport, определенный в листинге 9.6, для создания экземпляра объекта XMLHttpRequest. Затем обрабатывается запрос, и его заголовок Content-Type инициализируется, как в предыдущих примерах. Ссылка на объект хранится в локальной переменной request.

©На данном этапе решается задача обработки отклика. Вы наверняка пытаетесь догадаться, зачем была создана переменная oThis. Обратите внимание на следующую строку, где фигурирует анонимная функция, реагирующая на изменение статуса готовности (onreadystatechange) нашего объекта запроса и ссылающаяся на oThis. Данный прием называется замыканием. В силу того что внутренняя функция обращается к локальной переменной, создается неявный контекст выполнения (или область действия), позволяющий поддерживать ссылку после выхода из замыкающей функции. (Подробнее о замыкании речь пойдет в приложении Б.) Это позволяет реализовать обработку отклика Ajax посредством вызова метода первого класса на нашем объекте ajaxHelper.

ОНаконец мы отправляем запрос Ajax. Обратите внимание на то, что массив, созданный нами на этапе 1, передается методу queryString, который преобразовывает его в одну строку. Эта строка становится телом запроса Ajax. В действительности метод queryStringHe является частью открытого контракта, который мы обсуждали ранее, но относится к вспомогательным методам, облегчающим чтение и понимание кода. Рассмотрим его подробнее в листинге 9.8.

queryStrxng:

function(args) {

 

 

//

Постоянные

параметры

 

 

 

v a r r e q u e s t P a r a m s = [ ] ;

 

 

 

for ( v a r i

= 0

;

i < t h i s . r e q u e s t P a r a m s . l e n g t h

; i++

) {

 

r e q u e s t P a r a m s . p u s h ( t h i s . r e q u e s t P a r a m s [ i ] ) ;

 

 

 

}

 

 

 

 

 

//

Параметры

времени

выполнения

 

 

 

for ( v a r j

= 0

;

j < a r g s . l e n g t h ; j++ ) {

 

 

 

r e q u e s t P a r a m s . p u s h ( a r g s [ j ] ) ;

 

 

 

}

 

 

 

 

 

 

v a r q u e r y S t r i n g - " " ;

 

 

 

if ( r e q u e s t P a r a m s SS r e q u e s t P a r a m s . l e n g t h > 0 ) {

 

 

for ( v a r

i =

0

; i < r e q u e s t P a r a m s . l e n g t h ;

i++ )

{

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

371

queryString += requestParams[i] + •&';

}

queryString • queryString.substring{0, queryString.length-1);

}

return queryString;

Данный метод принимает параметры запроса, с которыми был создан объект net.ContentLoader, а также дополнительные параметры времени выполнения, переданные в метод sendRequest и помещенные им в общий массив. Затем массив обрабатывается и превращается в строку запроса. Результат описанных действий представлен ниже.

var helper = new net.ContentLoader{ someObj, someUrl, "POST",

["a-one", -b-twon]

);

var str = ajaxHelper.queryString(

["c-three", "d-four"]

);

str -> "a*=one&b=two&c=three&d=four"

Последнее, что нам требуется сделать для получения полнофункционального вспомогательного объекта, — это реализовать сотрудничество с компонентом с целью обработки отклика, поступающего от Ajax. Если вы были внимательны, то, возможно, уже знаете имя данного метода. Действительно, наш метод sendRequest уже задал, как он будет обрабатывать отклик свойства onreadystatechange запроса.

r e q u e s t . o n r e a d y s t a t e c h a n g e • f u n c t i o n ( ) { o T h i s . h a n d l e A j a x R e s p o n s e ( r e q u e s t )

}

Все правильно, осталось всего лишь реализовать метод handleAj axResponse. Реализация этого метода приведена в листинге 9.9.

handleAjaxResponse: function(request) {

if (

request.readyState == net.READi_STATE_COMPLETE ) {

if

( this.isSuccess(request)

)

// Компонент сообщения с откликом

 

t h i s . c o m p o n e n t . a j a x U p d a t e ( r e q u e s t ) ;

e l s e

 

 

// Компонент сообщения с ошибкой

 

 

t h i s . c o m p o n e n t . h a n d l e E r r o r ( r e q u e s t ) ;

}

 

 

 

},

 

 

 

i s S u c c e s s : f u n c t i o n ( r e q u e s t ) {

 

r e t u r n r e q u e s t . s t a t u s = e

0

 

II

( r e q u e s t . s t a t u s >=

200 &&

r e q u e s t . s t a t u s < 300);

Все, что делает метод, — проверяет, равно ли значение состояния readys- ' t a t e четырем (указывает на завершение), и уведомляет t h i s , component, что отклик доступен. Но мы еще не закончили. Существовало еще требование соответствующей обработки ошибок. Но что такое "соответствующая"? Этого мы сказать не можем. Обработка ошибок — это решение, которое нужно отложить для другого объекта. Следовательно, мы предполагаем, что наш клиент this, component, имеет метод handleError, который и выполняет необходимые действия, когда отклик Ajax поступает как-то не так. Этот компонент может в свою очередь делегировать решение другому объекту, но это уже не проблемы нашего вспомогательного объекта. Мы обеспечили механизм и позволим другому объекту обеспечить семантику. Как говорилось ранее мы предполагаем, что this . component имеет методы aj axupdate и handleError. Это неявный созданный нами контракт, поскольку JavaScript не относится к языкам со строгим контролем типов, которые могут принудительно вводить подобные ограничения.

Поздравляем! Вы трансформировали net .ContentLoader Б гибкий вспомогательный объект, который выполняет всю грязную работу Ajax для ваших DHTML-компонентов, лоддерживающих Ajax. А если у вас есть DHTMLкомпонент, который еще не поддерживает Ajax, теперь он станет проще! Кстати, нам еще нужно написать компонент двойных списков.

9.6.2. Создание компонента двойного списка

Мы немного поработали с объектом net.ContentLoader, чтобы существенно упростить нашу задачу, к которой теперь пора вернуться. Предположим, что нам, первоклассным разработчикам, поручили создать сценарий Связанного выбора, который можно использовать повторно во многнх^онтекстах в приложении или нескольких приложениях. В связи с этим для удовлетворения поставленным требованиям необходимо рассмотреть несколько вопросов.

• Предположим, мы не можем (или не хотим) вручную менять HTML-

. разметку окон выбора. Так может быть, например, если мы не отвечаем за производство разметки. Возможно, элемент select генерируется JSP или другим дескриптором на языке сервера. Возможно, какой-то дизайнер написал HTML-код, а мы желаем по возможности сохранить его без изменений, чтобы не допустить дополнительного этапа переверстки страницы.

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

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

Начав с первой задачи (максимально сохранить HTML-разметку), предположим, что характерным представителем HTML-кода, с которым нам придется работать, является разметка, приведенная в листинге 9.10.

<html>

<body>

<form name="Forml">

<select id="region" name="region" > <options... >

</select>

<select i d = " t e r r i t o r y " name="territory" /> </form>

</body>

</html>

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

Листинг 9.11. Модифицированная HTML-разметка . '5 !а ТЯИЯНИИТТ

<html>

<head>

<script>

function inj ectComponentBehaviors() { var doubleComboOptions = {};

< ! — Компонент DoubleCombo —>

new DoubleCombo('region', 'territory',

1DoubleComboXML.aspx', doubleComboOptions );

}

 

</script>

 

</head>

 

<body onload=

 

•injectComponentBehaviors()"

 

>

 

<form name="Forml">

 

<select id="region" narae="region" >

 

<option value="-l">Pic]c A Region</option>

 

<option value="1">Eastern</option>

 

<option value="2">Western</option>

 

<option value="3">Northern</option>

 

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

 

</select>

 

<select id="territory" name="territory" />

 

</form>

 

</body>

 

</html>

д

Листйнг-9,12. Компонент DoubieComboDoubleCombo function DoubleCombo( masterld, slaveld, url, options ) {
// Инициализация состояния
t h i s . m a s t e r - d o c u m e n t . g e t E l e m e n t B y l d ( m a s t e r l d ) ; t h i s . s l a v e = d o c u m e n t . g e t E l e m e n t B y I d ( s l a v e l d ) ;
t h i s . o p t i o n s = o p t i o n s ;
t h i s . a j a x H e l p e r " new n e t . C o n t e n t L o a d e r ( t h i s , u r l , "POST", o p t i o n s . r e q u e s t P a r a m e t e r s I I, И
// Инициализация поведения - — t h i s . i n i t i a l i z e B e h a v i o r ( ) ;
}

Разметка изменилась следующим образом

Создана функция, инжектирующая в документ требуемое нам поведение

К элементу тела, вызывающему данную функцию, добавлен обработчик onload.

Обратите внимание на то, что в элементе <body> страницы никаких модификаций нет. Как отмечалось ранее — это хорошо. Таким образом, мы уже удовлетворили первому требованию. Но давайте посмотрим на функцию injectComponentBehaviors() — похоже, что есть еще одна работа. Действительно, нам нужно создать объект JavaScript, именуемый DoubleCombo в котором было бы реализовано все поведение, необходимое для поддержки функциональных возможностей двойной комбинации.

Логика компонента DoubleCombo

Для начала рассмотрим внимательнее семантику создания нашего компонента. Функция injectComponentBehaviors() создает объект DoubleCombo, вызывая его конструктор. Этот конструктор определен в листинге 9.12.

. .

);

m

Данная конструкция должна быть вам уже знакомой; наша функция конструктора инициализирует состояние объекта DoubleCombo. Описания аргументов, которые необходимо передать конструктору, приведены в табл. 9.2.

Рассмотрим природу состояния, поддерживаемого объектом DoubleCombo, особенно URL и опции. Две данные составляющие состояния удовлетворяют второму указанному ранее функциональному требованию, т.е. наш компонент может задействовать для извлечения данных любой URL, кроме того, этот компонент можно настраивать с помощью параметра options. Пока что мы предполагаем, что в объекте опций найдем только одно — свойство requestParameters. Однако, поскольку параметр options — это просто общий объект, мы можем приписать ему любое свойство, необходимое для облегчения последующей настройки. Наиболее очевидными "довесками" объекта опций являются стилевое оформление классов CSS и прочее из этой же серии. Однако стиль и функции связных списков очевидно являются независимыми концепциями, поэтому решение вопросов стилевого оформления мы оставляем дизайнеру страницы.

Таблица 9.2. Описание аргументов дргумент Описание

m a s t e r l d Идентификатор элемента разметки, соответствующего основному элементу s e l e c t . Выполнение операции выбора на данном элементе определяет значения, отображаемые вторым элементом s e l e c t

s l a v e l d Идентификатор элемента разметки, соответствующею зависимому элементу s e l e c t . Значения этого элемента будут меняться в зависимости от выбора пользователем значения основного элемента s e l e c t

o p t i o n s Общий объект, предоставляющий другие данные, требуемые сценарием

Мы уверены, что для многих из вас самой интересной частью конструктора являются последние две строчки:

this.ajaxHelper = new net.ContentLoader( this, url, "POST", options.requestParameters || []

>;

Разумеется, мы знаем, что наш компонент требует возможностей Ajax. Благодаря везению и толике планирования мы уже имеем объект, выполняющий большую часть всей работы, выпадающей на долю Ajax, — речь идет об объекте net.ContentLoader, который мы предусмотрительно написали ранее. Объект DoubleCombo просто передает себя (посредством this) как параметр компонента вспомогательному объекту ContentLoader. Кроме того, как целевой URL запросов Ajax этому вспомогательному объекту передается параметр url, и с помощью строки "POST" задается метод HTTP-запроса.

Наконец, свойство requestParameters объекта опций (или пустой массив, если не была определена ни одна опция) передается как массив "постоянных" параметров, которые будут отправляться со всеми запросами Ajax. Напомним также, что поскольку мы передали this как аргумент компонента, объект DoubleCombo обязан реализовать указанный ранее неявный контракт с объектом net .ContentLoader. Другими словами, мы должны реализовать методы aj axUpdate () и handleError (). Чуть позже мы разберем этот момент подробно, а пока рассмотрим последнюю строку нашего конструктора:

this.initializeBehavior{);

Наконец-то наш конструктор делает что-то, похожее на желаемое поведение. Да, наступил момент, которого мы так долго ждали: реализация поведения. Все, что мы будем делать далее, непосредственно связано с обеспечением функциональных возможностей зависимых списков. Поэтому без лишних слов рассмотрим данный метод (а также все остальные методы DoubleCombo, которые нам потребуются). Благодаря тому что мы упорядочили всю инфраструктуру, наша задача уже не выглядит устрашающе. Помните, что все методы, которые встретятся при разборе данного примера, предполагаются внедренными в литеральный объект-прототип (точно так же, как мы делали при реализации net.ContentLoader).

DoubleCombo.prototype = { // все методы ... .

};

Ну что ж, заглянем под капюшон. Ниже приведен метод initializeBehavior().

initializeBehavior: function() { var oThis = this;

this.master.onchange = function() { oThis.masterComboChanged(); };

)/

Коротко и ясно. Данный метод помещает обработчик событий onchange в основной элемент select {ранее это делалось в самой разметке HTML). При срабатывании обработчик событий вызывает на нашем объекте masterComboChangedO другой метод.

masterComboChanged: function()

{

var query = this.master.options[

this.master.selectedlndex].value;

this.ajaxHelper.sendRequest(

'q=' + query );

Ь

 

Удивительно, но все по-прежнему коротко и ясно. Все, что требуется от этого метода, — это создать параметр запроса и отправить наш запрос Ajax. Поскольку вся работа Ajax была вынесена в другой объект, все действия формулируются с помощью одной строки кода. Напомним, что sendRequest {) создаст и отправит XMLHttpRequest, который направит отклик обратно нашему методу ajaxUpdate (). Запишем все сказанное.

ajaxUpdate: function(request) {

 

var slaveOptions = this.createOptions (

^/

request.responseXML.documentElement);

//Очистить существующие опции this.slave.length = 0;

//Заполнить новые опции

for ( var i = 0 ; i < slaveOptions.length ; i++ ) try{

this.slave.add(slaveOptions[i], null); }catch (e){

this.slave.add(slaveOptions[i] , -1) ;

}

ь

Данный метод принимает ответный XML-документ от объекта request и передает его методу createOptions(), который создает элементы option нашего зависимого элемента select. Затем метод просто очищает и повторно заполняет зависимый элемент select. Метод createOptions(), хотя я не является частью никакого публичного контракта, относится к вспомогательным методам, которые облегчают чтение и понимание кода. Его реализация и еще один вспомогательный метод, getElementContent (), показаны в листинге 9.13.

Листинг 9.13. Методы заполнения

createOptions: function (ajaxRespon.se) {

 

 

 

var newOptions = [];

 

 

 

var

entries

= ajaxResponse.getElementsByTagName('entry');

 

for

( var i — 0 ; i < entries.length ; i++

) {

 

 

var

text

= this.getElementContent(entries[i],

 

 

 

 

'optionText') ;

 

 

 

var

value

= this.getElementContent(entries[i],

 

 

 

 

'optionValue') ;

 

 

 

newOptions.push( new Option{ text, value

) );

 

}

 

 

 

 

 

 

return

newOptions;

 

 

ь

 

 

 

 

 

getElementContent: function(element,tagName)

{

 

 

var

childEleraent = element.getElementsByTagName(tagName)[0];

 

return

{childElement.text != undefined) ? childElement.text :

 

 

 

 

childElement.textContent;

Ь

 

 

 

 

m

 

Данные методы выполняют всю тяжелую работу, действительно извлекая значения из ответного документа XML и создавая по ним объекты опций. Напомним, что структура XML-отклика выглядит следующим образом:

<?xml version»'* 1.0" ?>

<selectChoice>

<e n t r y>

<o p t i o n T e x t > S e l e c t A T e r r i t o r y < / o p t i o n T e x t > <optionValue> - l</optionValue>

</entry>

<entry>

<o p t i o n T e x t > T e r r i t o r y D e s c r i p t i o n < / o p t i o n T e x t >

<o p t i o n V a l u e > T e r r i t o r y I D < / o p t i o n V a l u e > </entry>

</ s e l e c t C h o i c e >

Метод createOptions () последовательно проходит по всем элементам entry в XML-документе и извлекает текст из элементов optionText и optionValue посредством вспомогательного метода getElementContent (). Касательно метода getElementContent () стоит отметить только то, что он использует IE-совместимый атрибут text элемента XML, если он существует; в противном случае применяется стандартизованный W3C атрибут textContent.

Обработка ошибок

Ну вот и все. Вернее, почти все. Мы реализовали все линии поведения, необходимые для раскрытия потенциала данного компонента. Но постойте! Мы говорили, что обработка состояний ошибки также будет. Вы можете сказать, что нам требуется еще реализовать метод handleError (), чтобы с объектом net.ContentLoad.er можно было нормально работать. Ну так давайте реализуем его и действительно завершим эту задачу. Итак, какое восстанавливающее действие требуется при наличии ошибки? Пока что мы этого сказать не можем. Вообще-то, этот вопрос должно решать приложение, использующее

наш компонент DoubleComtoo. Похоже, что при такой формулировке нашлась работа нашему объекту опций (помните, мы передавали его конструктору?У Рассмотрим возможность такого контракта. Что будет, если мы создадим нащ компонент связных списков с кодом, подобным приведенному ниже?

function myApplicationErrorHandler(request)

{

//

Функция приложения, отвечающая

за обработку ошибок

}

 

 

 

var comboOptions = { requestParameters: [

 

 

"paraml=one", "param2=two" ],

errorHandler: myApplicationErrorHandler

 

};

 

 

 

v a r

doubleCombo • new DoubleCombo(

' r e g i o n ' ,

' t e r r i t o r y 1 ,

 

'DoubleComboXML.aspx',

 

 

comboOptions );

В этом сценарии, мы позволяем приложению определить функцию myApplicationErrorHandler (). Именно в реализацию данного метода мы можем поместить логику приложения, обрабатывающую ошибочные ситуации, Это может быть предупреждение. Или менее навязчивое сообщение "упс!" в стиле GMail. Суть такого решения заключается в том, что мы делегируем принятие решения приложению, использующему наш компонент. Как и ранее, мы предоставляем механизм и позволяем кому-то еще обеспечить семантику. Следовательно, теперь нам нужно написать метод handleError () объекта DoubleCombo.

handleError: function(request) { if ( this.options.errorHandler )

this.options.errorHandler(request);

}

Счастье компонента

Поздравляем! Наконец-то мы сделали все. У нас есть общий компонент, который мы можем создать с идентификаторами любых двух элементов select и некоторой информацией по конфигурации, также у нас есть возможность зависимого выбора. И тут... распахивается дверь1.

Пятница, 14:45, входит менеджер, ничего не смыслящий в программировании. "Джонсон, — восклицает он. — Нам нужна поддержка подтерриторий! ... И она должна быть готова к утру понедельника!" Драматическая пауза. "О-ох!" — выдавливаете вы из себя. Затем вы собираетесь и говорите: "Я сделаю это, сэр. Даже если мне придется проработать все выходные". Он вручает вам новый дизайн страницы:

<form>

<select id="region" name="region"><select> <select id="territory" name="territory"></select>

<select id="subTerritory" name="subTerritory"X/select> </form>

Начальство уходит. Вы открываете HTML-страницу в Emacs, поскольку так вам удобнее. Переходите сразу к заголовку. Курсор мигает — вы начинаете набирать.

<script>

function injectComponentBehaviors() {

var optsl *• { requestParameters: "master=region" }; var opts2 = { requestParameters: "master=territory" J; new DoubleCombo( 'region1,

'territory', 'DoubleComboXML.aspx', optsl );

new DoubleCombo( 'territory', 1subTerritory',

'DoubleComboXML.aspx', opts2 );

</script>

Вы нажимаете клавишу, запуская макрос, который отформатирует ваш код. Сохраняете. Бросаете через плечо: "Я буду работать из дома", проходя мимо офиса менеджера в 14:57. Дома вы плюхаетесь на диван и думаете про себя: "Да, я крут!" Ладно, хватит фантазий. Запомните, что вы сделали, и забудьте обо всем остальном.

9.7. Резюме

 

_

Двойная комбинация элементов select представляет собой эффективный метод создания для пользователя динамических элементов формы. Мы можем использовать обработчики событий JavaScript для отслеживания изменений в одном элементе select и инициации процесса обновления значений второго элемента. С помощью Ajax мы можем избежать длительного времени загрузки страницы, характерного для решений JavaScript. Используя Ajax, мы можем организовать запрос к базе данных, не отправляя всю страницу на сервер для дообработки и не разрушая взаимодействие пользователя с формой. Ajax позволяет создавать Web-приложения, более близкие к клиентским приложениям.

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

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