Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax_v_deystvii.pdf
Скачиваний:
38
Добавлен:
05.03.2016
Размер:
5.83 Mб
Скачать

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

var strText = this.getElementContent( entries[i1.getElementsByTagName('text') [ 0 ] );

var strValue = this.getElementContent{ entries[i].getElementsByTagName('value')[0]) ;

this.suggestions.push({ text: strText, value: strValue });

}

),

getElementContent: function(element) { return element.firstChild.data;

_ J

 

.

Поскольку мы собираемся сконцентрироваться исключительно на реализации в нужных местах механизмов Ajax, многие элементы будут рассмотрены на высоком уровне, а обработка ответа описана алгоритмически. Итак, сначала необходимо разобрать отклик посредством метода createSuggestions (), преобразовав его во внутреннее представление предлагаемых вариантов, содержащихся в свойстве suggestions. Это свойство представляет собой массив объектов, содержащих текст и значение (соответствуют элементам <text> и <value> каждого элемента <entry> XML-документа).

Остальная часть алгоритма, метод ajaxUpdate(), достаточно очевидна и понятна. Если предлагаемые варианты не найдены, всплывающее окно скрывается, а внутреннее значение, хранимое компонентом посредством скрытого поля, обнуляется. Если предполагаемые варианты найдены, создается раскрывающийся пользовательский интерфейс, заполненный предложениями, затем он отображается на экране с первой выделенной позицией. На этом этапе запрос считается обработанным, поэтому значение рассмотренного ранее свойства this.handlingRequest устанавливается равным false. Наконец, метод ajaxUpdatet) проверяет, имеются ли незавершенные запросы. Если да, значение метки pendingRequest устанавливается равным false, las - tRequestString присваивается текущее значение поля ввода и посредством sendRequestForSuggestions() инициируется следующий цикл запроса.

Таким образом, к концу третьего дня мы получили полный цикл запроса/ответа с поддержкой Ajax. За сегодняшний день сделано довольно много: мы разобрались в структуре с открытым исходным кодом, которая позволила полностью "активизировать Ajax" (требование 7), а также гарантировали возможность настройки и поддержки нескольких экземпляров на одной странице (требования 2 и 3). Собственно созданием, размещением, показом и сокрытием пользовательского интерфейса мы займемся на пятый день. Пока же рассмотрим события компонента и подготовим обработку действий с клавиатурой и мышью.

10.5.4. День 4: обработка событий

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

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

метода injectSuggestBehavior{). Данный код инициирует все модификации DOM существующей разметки, включая обработку событий, дополнительный ввод, и контейнер для предположений. Все эти модификации програь*: мируются, HTML-код страницы затрагиваться не будет (см. требование 1) Введение требуемого поведения показано в листинге 10.25.

Листинг 10.25. Введение требуемого поведения

injectSuggestBehavior:

function() {

 

 

if ( t h i s . i s I E ) {

 

 

//

Убрать взаимодействие

с I n t e r n e t E x p l o r e r

 

 

t h i s . t e x t l n p u t , a u t o c o m p l e t e = "off";

 

 

}

 

 

 

 

 

v a r

JceyEventHandler

=

 

 

 

new T e x t S u g g e s t K e y H a n d l e r ( t h i s ) ;

//

Создать контроллер

 

 

 

 

new

I n s e r t i o n . A f t e r (

t h i s . t e x t l n p u t ,

 

 

 

 

' < i n p u t t y p e = " t e x t " i d - " ' + t h i s . i d +

 

 

 

 

' _ p r e v e n t t s u b m i t ' +

 

 

 

 

' " s t y l e = " d i s p l a y : n o n e " / > ' ) ;

 

 

new

I n s e r t i o n . A f t e r (

t h i s . t e x t l n p u t ,

 

 

 

 

' < i n p u t t y p e = " h i d d e n " name="'+

 

 

 

 

t h i s . i d + ' _ h i d d e n ' +

 

 

 

 

1Tr

i d = ' " + t h i s . i d + l _ h i d d e n r + " ' / > ' );

 

 

t h i s . c r e a t e S u g g e s t i o n s D i v f ) ;

//

Создать пользовательский интерфейс

},

 

 

 

 

я

Данный метод вначале проверяет, не используется ли Internet Explorer. Если используется, то собственному свойству autocomplete присваивается значение off. Благодаря этому встроенные возможности автоматического заполнения не взаимодействуют с нашим всплывающим окном. Далее создается объект TextSuggestKeyHandler, который будет перенаправлять события нужным методам. Действительно, механика событий достаточно запутана, поэтому мы выделяем поведение в отдельный объект, который рассмотрим чуть позже. Следующий метод вводит несколько входных элементов в разметку. Напомним, что в предыдущем цикле разработки кода мы добавили скрытое поле ввода для хранения значения компонента и невидимое текстовое поле, препятствующее отправке формы при нажатии клавиши <Enter>. Поскольку первое требование технической задачи запрещает вмешиваться в HTML-код, вся "грязная работа" выполняется программно — с помощью двух вызовов Insertion.After(). Возможность использования Insertion. After () появилась благодаря библиотеке Prototype. Наконец, вызывается функция createSuggestionsDiv(), создающая элемент div, вмещающий пользовательский интерфейс с предлагаемыми вариантами.

Объект TextSuggestKeyHandler

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

Глава 10. Опережающий ввоо 4*э

01 можно еще отделить все компоненты дизайна, создав явные классы модели 1( представления и обеспечив полный шаблон MVC Это предлагается сделать читателю в качестве самостоятельного упражнения, но в главе 13 будет показано, как разбить архитектуру приложения чтения RSS-сообщений на набор классов, согласующихся с традиционным шаблоном MVC.

Контроллер создается точно так же, как и основной класс, — с использованием Class.create() и метода i n i t i a l i z e ( ) . Конструктор TextSuggestKeyHandler показан в листинге 10.26.

Листинг 10.26. Конструктор TextSuggestKeyHandler TextSuggestKeyHandler = Class.create(); TextSuggestKeyHandler.prototype = {

initialize:

function( textSuggest ) {

this.textSuggest = textSuggest;

this.input = this.textSuggest.textlnput;

// Ссылка на

TextSuggest

t h i s . a d d K e y H a n d l i n g ( ) ;

 

h

 

 

//

остальная часть API

_ Ь

 

 

щ

В процессе создания контроллер использует ссылку на компонент опережающего ввода, а также "родное" поле ввода HTML-формы. Затем с помощью this.addKeyHandlingO он добавляет в поле ввода обработчики событий. Метод addKeyHandling() показан в листинге 10.27.

Листинг 10.27. Обработчик действий с клавиатурой

addKeyHandling: function() { this.input.onkeyup =

this.keyupHandler.bindAsEventListener(this); this.input.onkeydown =

this.keydownHandler.bindAsEventListener(this); this.input.onblur =

this.onblurHandler.bindAsEventListener(this); if ( this.isOpera )

this.input,onkeypress = this.keyupHandler.bindAsEventListener(this);

В данном методе устанавливаются все события, наступление которых требуется отслеживать, а также код для браузера Opera, упоминавшийся в первом цикле разработки данного сценария. Напомним, что метод bindAsEventListener ( ) представляет собой механизм замыкания, полученный благодаря библиотеке Prototype. Данный механизм позволяет нашим обработчикам вызывать методы первого класса на контроллере и сводит к общему знаменателю модели событий Internet Explorer и W3C. Очень хорошо. Методы keyupHandlerO, keydownHandler(), onblurHandler () и их вспомогательные методы представляют собой реструктуризированный вариант уже рассмотренного кода (с минимальными изменениями). Все эти методы будут описаны

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

keydownHandler: function(e) { var upArrow = 38;

var downArrow = 4 0 ;

if ( e.keyCode =- upArrow ) { this.textSuggest.moveSelectionUp();

setTimeout( this.moveCaretToEnd.bind(this), 1 );

}

 

 

 

else if ( e.keyCode == downArrow )

{

 

 

this.textSuggest.moveSelectionDown();

}

 

 

—h

 

.

 

 

 

 

Наиболее существенные отличия от исходного сценария с точки зрения функциональных возможностей претерпела обработка клавиш со стрелками. В компоненте TextSuggest клавиши со стрелками управляют движением выделения, основываясь на событии onkeydown, а не на onkeyup. Это сделано исключительно для повышения практичности. Иногда пользователя сбивает с толку то, что выделение остается на своем месте при нажатии клавиши и начинает перемещаться после того, как клавиша отпущена. В общем, перемещение выделения обрабатывается методом keydownHandler (). Обратите внимание на то, что методы управления выделением являются методами компонента TextSuggest. Поскольку контроллер во время создания записал ссылку на компонент, он может вызывать эти методы посредством объектной ссылки t h i s . textSuggest. Для полноты изложения мы приводим в листинге 10.29 методы управления визуальным выделением.

Листинг 10.29. Методы TextSuggest, отвечающие за визуальное выделение п

raoveSelectionUp: function{)

{

if ( this . selectedlnde x > 0

) {

this . updateSelection(this . selectedlndex - 1);

}

ь

moveSelectionDown: function() {

if ( this.selectedlndex < (this.suggestions.length - 1) ) { this.updateSelection<this.selectedlndex + 1);

}

ь

updateSelection: function(n) {

var span = $(this.id + "_"+this.selectedlndex); if (span){

span.style.backgroundColor = ""; // Очистить предыдущее выделение

}

this.selectedlndex = n;

var span = 5(this.id+"_"+this.selectedlndex); if (span){

span.style.backgroundColor = this,options.selectionColor;

}

Реальную работу по изменению визуального состояния выделенного элемента выполняет метод updateSelection(). Он обновляет элемент span, созданный в списке выбора (соответствующий код мы напишем на пятый день), и устанавливает его свойство style.backgroundColor равным значению, заданному как options.selectionColor в объекте Configuration нашего компонента.

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

Листинг 10.30. Метод moveCaretToEnd () компонента TextSuggest • ' 9 | Щ Н moveCaretToEnd: function() {

v a r роз = t h i s . i n p u t . v a l u e . l e n g t h ; if ( t h i s . i n p u t . s e t S e l e c t i o n R a n g e ) {

t h i s . i n p u t . s e t S e l e c t i o n R a n g e ( p o s , p o s ) ;

}

e l s e i f ( t h i s . i n p u t . c r e a t e T e x t R a n g e ) {

v a r m = t h i s . i n p u t . c r e a t e T e x t R a n g e { ) ; m . m o v e S t a r t ( ' c h a r a c t e r 1 , p o s ) ; ra.collapse();

m . s e l e c t ( ) ;

Ь

}

ж

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

Листинг 10.31. Обработка в TextSuggest отпускания клавиш

,

у

keyupHandler: function(e) {

 

 

 

if ( this.input.length == 0 && !this.isOpera )

 

 

this.textSuggest.hideSuggestions();

 

 

if

( 'this.handledSpecialKeys(e) )

 

 

this.textSuggest.handleTextlnput() ;

 

 

Ь

 

 

 

 

 

handledSpecialKeys: function(e)

{

 

 

var

enterKey =

13;

 

 

 

var

upArrow =

38;

 

 

 

var downArrow • 40;

 

 

 

if

( e.keyCode == upArrow II

e.keyCode == downArrow )

{

 

1 return true;

else if ( e.keyCode == enterKey ) { this.textSuggest.setInputFromSelection{); return true;

}

return false;

_ ±

 

^

 

Обработчик отпускания клавиш вначале проверяет, содержится ли текст в поле ввода. Если нет, он сообщает компоненту TextSuggest скрыть всплывающий список предлагаемых вариантов. Далее обработчик проверяет, не была ли нажата одна из специальных клавиш: клавиша со стрелкой вверх, клавиша со стрелкой вниз или <Enter>. Если была нажата одна из клавиш со стрелками, метод не выполняет никаких действий, поскольку нажатие этой клавиши уже было обработано. Однако если была нажата клавиша <Enter>, метод сообщает компоненту TextSuggest установить входное значение на основе выбранной в текущий момент позиции списка предположений. Наконец, если поле ввода содержит значение и нажатая клавиша не является специальной, то обработчик сообщает компоненту TextSuggest о наличии некоторого входа, который нужно обработать с помощью метода textsugcrest.handleTextInput(). Данный метод компонента TextSuggest в конечном счете активизирует структуру Ajax, которую мы успешно настраивали вчера. Код обработчика handleText!nput() представлен в листинге 10.32.

;'• Листинг 10.32. Обработчик ввода текста "ШЯШШШИИВ;. > handleTextlnput: function{) {

var previousRequest = this.lastRequestString;

//

Предыдущее запрашиваемое значение

 

 

 

 

 

t h i s . l a s t R e q u e s t S t r i n g =

 

 

 

 

 

 

t h i s . t e x t I n p u t . v a l u e ;

 

 

 

 

 

//

Текущее запрашиваемое

значение

 

 

 

 

 

 

if { t h i s . l a s t R e q u e s t S t r i n g == "" )

 

 

 

 

 

t h i s . h i d e S u g g e s t i o n s ( ) ;

 

 

 

 

 

 

e l s e if ( t h i s . l a s t R e q u e s t S t r i n g != p r e v i o u s R e q u e s t ) {

 

 

 

 

t h i s . s e n d R e q u e s t F o r S u g g e s t i o n s ( ) ;

 

 

 

//

Запрос данных средствами Ajax

 

 

 

 

 

 

}

 

 

 

 

 

 

- ±

 

 

 

 

 

 

 

M

Вначале

метод handleTextlnputО

устанавливает

значение

локальной

переменной

previousRequest

равным

предыдущему

значению

t h i s . l a s t -

RequestString. Затем он устанавливает значение свойства lastRequestString равным текущему содержимому поля ввода, так что теперь можно сравнить эти два значения и убедиться, что мы не пытаемся запросить ТУ информацию, которая у нас уже есть. Если запрос представляет собой пустую строку, всплывающий список будет скрыт. Если запрос отправлен для получения новой информации, метод handleTextlnput () вызывает ме-

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

т од sendRequestForSuggestions() (который мы написали вчера) для вызова основанного на Ajax источника данных и получения с сервера ряда предположений. Если запрос идентичен последнему посланному запросу, никакие действия не предпринимаются. Похоже, что наконец-то фрагменты начинает собираться в общую картину. Создание, настройка конфигурации, работа инфраструктуры Ajax, обработка событий — мы действуем так, как будто знали, что придется делать. Идем впритык — сейчас уже четвертый день работы!

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

onblurHandler: function(e) {

if ( this.textSuggest.suggestionsDiv.style.display == " ) this.textSuggest.setlnputFromSelection();

this.textSuggest.hideSuggestions();

}

Обработчики onblurHandler и handledspecialKeys обращаются к методу компонента TextSuggest, который мы еще не рассматривали, — setlnputFromSelection ( ). По сути, этот метод делает то же, что делала ранее функция SetText () — принимает выделенное в текущий момент предположение; присваивает свой текст полю ввода, а значение — скрытому полю; скрывает список предположений. Реализация этого метода приведена ниже.

setlnputFromSelection: function() {

var hiddenlnput « $( this.id + "_hidden" );

var suggestion ~ this.suggestions[ this.selectedlndex ]; this.textlnput.value = suggestion.text;

//Обновить видимое значение hiddenlnput.value = suggestion.value;

//Обновить скрытое значение

this.hideSuggestions();

}

Возможно, для выполнения всего, что было запланировано на сегодня, пришлось немного поработать сверхурочно. Однако в результате мы создали класс controller, обрабатывающий все, что касается управления событиями. Для автоматического создания замыканий и унификации моделей событий Internet Explorer и W3C мы использовали метод bindAsEventListener() библиотеки Prototype. Мы реализовали обработчики нажатия/отпускания клавиш, скрывающие в себе все сложности обработки выделенной позиции и обычного текстового ввода. Мы гарантировали, что запросы будут отправляться только для получения новой информации; разобрались с показом и сокрытием в нужные моменты пользовательского интерфейса; программно обновили DOM для управления скрытым входным значением и невидимым текстовым полем, предотвращающим отправку формы при нажатии клавиши <Enter>. Кроме того, мы обработали обновление скрытого и видимого значений компонента TextSuggest. На пятый день мы доведем до ума и реструктуризируем наш компонент, реализовав все методы, требуемые для создания

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]