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

Глава 10. Опережающий ввод АЛЛ

Мы можем предложить более продуманное решение этой проблемы, введя в специальный объект дополнительный параметр и отправив его на сервер. Есть и другая возможность — работать с тем, что мы имеем, и внести в код минимальные изменения. В таком случае простое решение включает изменение одной строки кода и добавление оператора if в код серверной части сценария.

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

Листинг 10.20. Измененная функция TypeAhead() function TypeAhead(xStrText){

 

var strParams = "q=" + xStrText + "&where=" +

 

theTextBox.obj.matchAnywhere + "&name=" + theTextBox.name;

 

var loaderl = new net.ContentLoader(theTextBox.obj.serverCode,

_}

BuildChoices,null,"POST",strParams);

m

 

 

Немного изменив переменную strParams в функции TypeAhead(), в параметрах формы, отправляемых на сервер, мы теперь передаем имя текстового окна. Это означает, что мы можем обращаться к этому значению на сервере и использовать оператор if-else либо case для запуска другого запроса. В таком случае нам не требуется реализовывать несколько страниц, соответствующих различным элементам.

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

Разработав достаточно сильный набор функций, обеспечивающих возможности опережающего ввода, мы можем подумать, как реструктуризировать все эти возможности в более удобном для использования виде. То, что мы создали на текущий момент, обеспечивает функциональные возможности, необходимые для предоставления на выбор ряда вариантов. Однако данная структура имеет свои недостатки с точки зрения работы, требуемой от разработчика при внедрении ее на Web-страницу (или на 20-30 Web-страниц).

Давайте на минуту представим, что перед нами, как перед главным архитектором использующей Ajax Web-структуры, поставлена задача написать компонент опережающего ввода, который будет использовать вся компания. После завершения вводного собрания мы получаем краткий перечень функциональных требований. Не совсем еще понимая суть дела, мы просматриваем этот документ (табл. 10.2).

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

412

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

 

Таблица 10.2. Наши функциональные требования

 

Номер Описание требования

Приоритет

1

Компонент должен работать с существующей разметкой HTML, не

1

 

требуя никакого ее изменения. Допускается лишь простая модификация

 

 

заголовка с целью внедрения линии поведения компонента

 

2

Компонент должен без дополнительных усилий поддерживать

1

 

многократное использование на одной странице

 

3Должна существовать возможность индивидуальной настройки каждого 1 экземпляра компонента Под этим имеются в виду как аспекты поведения (например, учет регистра, поиск с любого места), так и стилевое оформление CSS

4

Компонент не должен вводить глобальные переменные. Компания

1

 

пользуется сторонними библиотеками JavaScript, и глобальное

 

 

пространство имен уже достаточно засорено. Использование любых

 

 

глобальных имен (кроме названия самого компонента) строго

 

 

запрещено

 

5

Компонент должен предоставлять разумные значения по умолчанию

1

 

для всех конфигурационных опций

 

6

Компонент должен работать в Internet Explorer и Firefox

1

7

Для уменьшения работ по кодированию, требуемых для улучшения

1

 

качества и надежности решения, компонент должен быть создан на

 

 

основе структуры с открытым исходным кодом

 

8

Кстати, если получится, сделайте это до конца недели

1

вания. Несмотря на то что мы проделали немалую работу, имеющийся сценарий не удовлетворяет даже половине из них. Сценарий уже готов, поэтому о требовании 7 можно забыть: нам не нужно уменьшать объем работ. Очевидно, что требованию 8 сценарий также удовлетворяет (по той же причине). Он поддерживает различные браузеры, поэтому снимаем и требование 6. А вот что касается остального, то определенную работу проделать все же придется. У нас есть всего неделя, поэтому начнем.

10.5.1. День 1: план разработки компонента TextSuggest

Прежде всего нам нужно определиться с тем, как поднять производительность и уложиться в отведенное время. Один из лучших способов — переложить работу на другихЕсли кто-то может сделать часть работы, пусть он ее для нас сделает. В данном случае мы собираемся воспользоваться библиотекой с открытым исходным кодом Rico (http: //openrico. org) и расширением Prototype.js (http://prototype.conio.net/). Библиотека Rico предлагает некоторую инфраструктуру Ajax, эффекты и вспомогательные методы, которые повысят скорость нашей разработки. Prototype предлагает инфраструктуру Для прекрасных синтаксических идиом, благодаря которым наш код будет выглядеть понятнее и потребует меньше времени на разработку. Поэтому давайте внимательно изучим последствия использования Prototype и Rico.

Глава 10 Опережающий ввод 413

prototype

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

Объект Class

Объект Class, представленный в библиотеке Prototype, имеет единственный метод create (), отвечающий за создание экземпляров, которые могут иметь любое число методов. Метод create {) возвращает функцию, вызывающую другой метод того же объекта — i n i t i a l i z e (). Звучит немного сложно, но на практике все просто По сути, таким образом формируется синтаксическая основа для задания типов в JavaScript. Идиома выглядит следующим образом:

var TextSuggest = Class.create(); TextSuggest.prototype = {

// Вызывается в процессе создания

initialize: function( pi, p2, p3 ) {

h

};

В данном фрагменте кода создается то, что можно считать "классом" (хотя сам язык не поддерживает такой концепции), и определяется функцияконструктор i n i t i a l i z e (). Клиент компонента может создавать экземпляры класса с помощью приведенной ниже строки кода.

var textSuggest = new TextSuggest(pi, p2, p3);

Метод extend О

Библиотека Prototype расширяет базовый объект JavaScript и добавляет метод, именуемый extend(), открывая этот метод для всех объектов. Метод extend () принимает в качестве параметров два объекта: базовый объект и объект, который будет его расширять. Свойства расширяющего объекта переносятся на базовый объект. Это позволяет использовать механизм расширения объектов на уровне экземпляров. Этой возможностью мы воспользуемся позже, когда будем реализовывать для настраиваемых параметров компонента TextSuggest значения по умолчанию.

Метод bind/bindAsEventListener()

Библиотека Prototype также добавляет два метода к объекту Function: bind() и bindAsEventListener(). Эти методы предлагают синтаксически элегантный способ создания замыкания функций. Напомним, как мы создавали замыкания в других примерах:

oThis • this;

this.onclick = function() { oThis.callSomeMethod() };

С помощью метода bind() библиотеки Prototype того же результата можно добиться гораздо проще.

this.onclick = this . callSomeMethod.binci( this);

API bindAsEventHandler() передает методу объект Event и сглаживает различия между Internet Explorer и стандартизованной W3C моделью событий!

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

Метод $ — синтаксическая "конфетка"

В JavaScript в названиях методов можно использовать определенные специальные символы, например знак доллара ($). В библиотеке Prototype этот малоизвестный факт используется для инкапсуляции в метод одной из наиболее распространенных задач в DHTML-программировании: извлечения элемента из документа на основе его идентификатора. Таким образом, в нашем коде мы сможем писать конструкции следующего типа:

$('textField').value = aNewValue;

При этом мы обходимся без указанных ниже громоздких структур.

var textField = document.getElementByldf'textField1 ) textField.value = aNewValue;

Rico

Используя Rico, мы получаем Prototype бесплатно. Посмотрим, что нам требуется от Rico. Rico предлагает богатый набор линий поведения, возможностей перетаскивания и кинематических эффектов, но поскольку мы пишем компонент, использующий единственное текстовое поле, то большинство из доступных возможностей нам не понадобится. Однако еще есть прекрасный обработчик Ajax и некоторые вспомогательные методы, предлагаемые Rico. Вспомогательные методы Rico мы рассмотрим по ходу разбора примера, а сейчас остановимся на предлагаемой Rico инфраструктуре Ajax. Возможности Ajax Rico публикуются посредством единственного объекта, доступного для документа ajaxEngine. API ajaxEngine предоставляет поддержку для регистрации логических имен для запросов и регистрации объектов, знающих, как обрабатывать ответы Ajax. Рассмотрим, например, следующий код:

ajaxEngine.registerRequest( 'getlnvoiceData', 'someLongGnarlyUrl.do' ) ;

ajaxEngine.registerAjaxObject( 'xyz', someObject );

В первой строке кода регистрируется логическое имя потенциально громоздкого URL Ajax. Далее при отправке запросов можно использовать это логическое имя, не отслеживая упомянутый громоздкий URL. Пример такого использования приведен ниже.

ajaxEngine.sendRequest('getlnvoiceData', request parameters . . . );

Метод registerRequest () локализует использование указателей URL — теперь они встречаются только в одном месте, обычно это обработчик событий onload в разделе тела. Если URL требуется изменить, это можно сделать в месте регистрации, не затрагивая остальную часть кода-

Метод registerAjaxObject() иллюстрирует регистрацию объекта обработки Ajax. В предыдущем примере подразумевалось, что в ответах необходимо обращаться к объектной ссылке someObject с помощью логического имени xyz, причем эта ссылка необходима для обработки ответов Ajax посредством метода ajaxUpdate ().

Исходя из того, что мы используем описанные функциональные возможности объекта ajaxEngine, нам осталось только рассмотреть ответный XML-

Глава 10 Опережающий ввод 415

документ, ожидаемый процессором Ajax. Этот документ немного отличается от динамически генерируемого сценария JavaScript, который возвращался в предыдущей версии данного примера, но Rico ожидает получить XML. Все элементы <response> документа должны находиться внутри элемента верхнего уровня <ajax-response>. Внутри указанного элемента сервер может возвращать столько элементов <response>, сколько требует приложение. Такая возможность очень удобна, поскольку позволяет серверу возвращать ответы, обрабатываемые различными объектами, обновляющими потенциально несвязанные области Web-страницы, — например, для обновления области состояния, области данных и конечной области вывода. XML-документ для предыдущего примера приведен ниже.

<аj ax-response>

<response type="object" id="xyz">

. . . the r e s t of the XML response as normal . . .

</response> <response ... >

more response elements if needed.. </response>

</ajax-response>

Данный XML-документ указывает ajaxEngine, что данный запрос должен обработать объект, зарегистрированный с идентификатором xyz. Процессор Ajax находит объект, зарегистрированный с именем xyz, и передает содержимое соответствующего элемента <response> методу ajaxUpdate().

Вот и все. День получился довольно коротким. Некоторое время мы потратили на изучение структур с открытым исходным кодом, на которые мы можем опираться, и разработали план внедрения их в требуемый компонент. Мы еще не написали ни строчки кода, но решили, как ускорить дальнейшую работу над проектом. Кроме того, мы подобрали платформу, которая поможет повысить производительность работы (удовлетворив при этом требованию 7 технического задания). Кодирование начнется завтра.

10.5.2.День 2: создание TextSuggest — понятного

инастраиваемого компонента

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

Требование 1. Компонент должен работать с существующей разметкой HTML, не требуя никакого ее изменения. Допускается лишь простая модификация заголовка для внедрения линии поведения компонента.

Из-за этого требования мы оставляем нетронутым практически все, что находится внутри элемента <body>. Поэтому предположим, что нам требуется ввести сценарий в HTML посредством HTML-кода, подобного приведенному в листинге 10.21.

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

Листинг 10.21. HTML-разметка компонента TextSuggest

<html>

 

 

 

<head>

 

 

 

<script>

 

 

 

v a r s u g g e s t O p t i o n s = { / * d e t a i l s

to

come*/ };

f u n c t i o n

i n j e c t S u g g e s t B e h a v i o r ( ) {

//

Создать компонент в <head>

sugges t

= new TextSuggest(

 

 

1 f i e l d l ' , ' t y p e A h e a d D a t a . a s p x ' , s u g g e s t O p t i o n s ) ;

}>;

</ s c r i p t > </head>

<body o n l o a d = " i n j e c t S u g g e s t B e h a v i o r ( ) " > <form name="Forml">

AutoComplete Text Box:

< i n p u t t y p e = " t e x t " id—"fieldl" n a m e = " t x t U s e r I n p u t " > _ - </form>

</body>

</html>

Смысл приведенного HTML-кода заключается в том, что нам требуется создать объект с идентификатором текстового поля, к которому мы будем привязаны, указателем URL источника данных Ajax и набором конфигурационных объектов, которые еще будут заданы. (Чтобы это сработало, текстовое поле должно иметь идентификатор.) Все, что находится внутри элемента <body>, остается без изменений. Разобравшись с этим, займемся конструктором. Имя создаваемого компонента TextSuggest помещается в глобальное пространство имен с помощью функции-конструктора, которая (как вы помните) генерируется методом Clas s. create {) библиотеки Prototype, как показано в листинге 10.22.

Листинг 10.22. Конструктор TextSuggest

TextSuggest = Class.create(); TextSuggest.prototype = { initialize:

function{anld, url,

options) {//О Ссылка на входной элемент this.id = anld;

this.textlnput = $(this.id);

var browser = navigator.userAgent.toLowerCase();

//© Детектировать тип браузера this.isIE =

browser.indexOff"msie") != -1; this-isOpera =

browser.indexOf{"opera")!= -1; // © Установить значения по умолчанию

this.suggestions = []; this.setOptions(options); this.initAjax(url);

Ьthis.inj ectSuggestBehavior();

}±11

 

 

 

Глава 10. Опережающий ввод 417

Разберем этот конструктор. Как упоминалось ранее, конструктору передается идентификатор текстового ввода, с которым нужно связать предложение вариантов. Для поля ввода хранится ссылка как на идентификатор, так л на элемент DOM О. Далее определяется браузер пользователя и записывается состояние, которое понадобится компоненту позже, когда потребуется информация о среде времени выполнения этого браузера ©. В данном случае специальный код требуется только для Internet Explorer и Opera, поэтому проверяется использование только этих браузеров.

Настройка Ajax и введение в код нужной линии поведения будет рассмотрен позже ©. Пока же (до конца дня) сосредоточимся на возможности настройки компонентов. Как вы помните, ранее была создана функция Setproperties (), содержащая все настраиваемые аспекты нашего сценария.

function SetProperties

(xElem, xHidden, xserverCode, xignoreCase, xmatchAnywhere, xmatchTextBoxWidth, xshowNoMatchMessage, xnoMatchingDataMessage, xuseTimeout, xtheVisibleTime){

}

Написанный код удовлетворяет требованию, которое касается возможности настройки, но не обеспечивает удобный API или подходящие значения по умолчанию. Для решения этих проблем вводится объект опций, который передается конструктору. Объект опций содержит свойство для каждого конфигурационного параметра компонента опережающего ввода. Теперь нужно заполнить опции конфигурационными параметрами.

var suggestOptiona - { matchAnywhere : true, ignoreCase : true }; function injectSuggestBehavior() {

suggest = new TextSuggest('fieldl', ' typeAheadXML.aspx', suggestOptions ); } ) ;

Данная простая идиома предлагается следующие дополнительные преимущества.

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

Можно вводить дополнительные конфигурационные параметры, не меняя контракт конструктора.

Можно написать изящную функцию setoptions(), предоставляющую значения по умолчанию для всех незаданных свойств, и позволить пользователю задавать только те свойства, которые он желает изменить.

Впоследнем пункте описано именно то, что делает метод setOptions (), включенный в конструктор ранее ©. Разберем его.

setOptions: function(options) {

t h i s , o p t i o n s • { suggestDivClassName: ' s u g g e s t D i v ' ,

ял а часть IV. Ajax в примерах

suggestionClassName: 'suggestion', matchClassName : 'match', matchTextWidth : true, selectionColor : '#ЫсО9С, matchAnywhere : false,

ignoreCase : false, count : 10

} . e x t e n d ( o p t i o n s | I (}) ;

Ь

В данном коде задаются все свойства объекта опций, имеющие подходящие значения по умолчанию. Затем вызывается метод extend () библиотека Prototype, который переопределяет свойства, переданные во время создания.

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

Виспользованном примере были переопределены булевы свойства matchAnywhere и ignoreCase (новые значения — true). Значения конфигурационных свойств объясняются в табл. 10.3.

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

Требование 3. Должна существовать возможность индивидуальной настройки каждого экземпляра компонента. Под этим имеются в виду как аспекты поведения (например, учет регистра, поиск с любого места), так и стилевое оформление CSS.

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

В готовом компоненте описанный механизм конфигураций будет использоваться для настройки на уровне экземпляров поведения (например, чув-J ствительности к регистру) и стилевого оформления (например, имен клас-! сов CSS).

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

10.5.3. День 3: включаем Ajax

Ну что, подключим к работе Ajax? Без Ajax компонент TextSuggest подобен гамбургеру без говядины. Мы не относимся неуважительно к вегетарианцам, но сейчас пришло время говядины. Вы уже видели подготовку к включению Ajax при разборе конструктора. Как вы помните, в конструктор был включен метод initAjaxf), который выполняет настройку, требуемую для обсуждавшейся выше поддержки Ajax. Реализация данного метода приведена ниже.

initAjax: function(url) {

ajaxEngine.registerRequest( this.id + '_request', url ); ajaxEngine.registerAjaxObject{ this.id + '_updater', this );},

Глава 10. Опережающий ввод 419

Таблица 10.3. Значения конфигурационных свойств

Значение

Объяснение

u ggestDivClassName

Задает имя класса CSS для элемента div, в котором будут

 

содержаться предлагаемые варианты

u ggestionClassName

Задает имя класса CSS для элемента span, генерируемого

 

для каждого предлагаемого варианта

matchClassName

Задает имя класса CSS элемента span, содержащего

 

фрагмент предлагаемого варианта, согласующийся с вводом

 

пользователя

inatchTextwidth

Булево значение, указывающее, должен ли размер элемента

 

d i v , генерируемого для списка предположений,

 

согласовываться с шириной текстового поля, с которым он

 

соотнесен

s e l e c t i o n C o l o r

Задает шестнадцатеричное значение (или любое другое

 

приемлемое значение, используемое CSS для задания цвета),

 

определяющее цвет фона списка предлагаемых вариантов

matchAnywhere

Булево значение, которое задает, с какого места строки должно

 

выполняться сопоставление, — с начала или с любой позиции

ignoreCase

Булево значение, указывающее, должен ли учитываться

 

регистр при поиске соответствий

count

Максимальное число показываемых предположений

Напомним, что метод registerRequest () предоставляет аппарату Ajax логическое имя указателей URL, которые будут вызываться для данного запроса посредством метода sendRequest{). При условии, что нам требуется поддержка нескольких компонентов опережающего ввода на странице, имеющих различные указатели URL (но использующие единственный элемент ajaxEngine), необходимо сгенерировать для каждого из них уникальное логическое имя. Таким образом, имя для запроса мы генерируем, основываясь на идентификаторе компонента, который считается уникальным. Тот же принцип применяется при обработке регистрации. Мы регистрируем this как объект, обрабатывающий ответные сообщения, направленные к сгенерированному нами идентификатору.

Пожалуй, стоит проиллюстрировать сказанное на примере. Предположим, что мы привязали предложение вариантов к полю с идентификатором id='fieldl', а затем эффективно зарегистрировали себя как 'fieldl_updater'. XML-документ, возвращаемый этому компоненту, должен содержать элемент, выглядящий примерно так, как показано ниже.

<ajax-response>

<response type='object' id= 'fieldl^updater' >.

. . . тот же xml - текст, что и р а н е е . </response>

</ajax - response>

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

Во внутреннем представлении запросы отправляются следующим образом; Щ aj axEngine.sendRequest{

'fieldl^request',lparaml=vall1, 'param2=val2', . . .

);

Учитывая вышесказанное, для получения Ajax-модификации нашего компонента необходимо отправить запрос и обработать ответный документ. Посмотрим, как это можно сделать.

Предлагаемый текст — отправка запроса Ajax

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

sendRequestForSuggestions ()

и реализуем ее следующим образом:

sendRequestForSuggestions;

function() {

if ( this.handlingRequest ) {

this.pendingRequest

=

true; return;

}

 

 

this.handlingRequest

= true;

this.callRicoAjaxEngine();

Ь

Единственное, что делает приведенный код, — это вызывает th.is.callRicoAj axEngine () при условии, что запрос не обработан. Этот простой механизм присваивает внутреннему булеву свойству this.handlingRequest значение true после создания запроса Ajax и false (рассмотрено ниже) — после обработки запроса. Если метод вызывается в момент обработки запроса, булеву свойству this.pendingRequest присваивается значение true. Это состояние сообщает обработчику, что, возможно, ему придется отправить еще один запрос после обработки текущего. Далее мы рассмотрим метод callRicoAj axEngine {), приведенный в листинге 10.23.

Листинг 10.23. Использование a j axEngine (Rico)

callRicoAjaxEngine: function() { // Построить массив параметров

var callParms = [];

callParms.push( this.id + '^request1); callParms.push{ T id= < + this.id); callParms.push( 'count=' + this.options.count);

callParms-push( 'query=' + this.lastRequestString); callParms.push( 'match_anywhere=l + this.options.matchAnywhere); callParms.push( 'ignore_case=' + this.options.ignoreCase);

var additionalParms = this.options.requestParameters t{ []; for( var i=0 ; i < additionalParms.length ; i++ )

callParms.push{additionalParms[i]);