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

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

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

10.5.5. День 5: пользовательский интерфейс всплывающего окна с предлагаемыми вариантами

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

Создание пользовательского интерфейса всплывающего окна с предлагаемыми вариантами. Это подразумевает создание элемента div для списка

иэлемента span для каждого варианта.

Размещение всплывающего окна в нужном месте.

Заполнение всплывающего окна предлагаемыми вариантами.

Показ и сокрытие вариантов.

Создание всплывающего окна с предлагаемыми вариантами

Вернемся немного назад и рассмотрим реализацию метода injectSuggestBehavior (). Напомним, что данный код представлял собой точку входа для всех манипуляций DOM, производимых компонентом TextSuggest.

injectSuggestBehavior:

function{) {

// Введение

поведения DOM HTML . . .

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

Ь

 

 

В последней

строке

метода injectSuggestBehavior() вызывается ме-

тод createSuggestionsDiv(), создающий самый внешний контейнер div всплывающего окна с предлагаемыми вариантами. Поскольку это контейнер всех артефактов GUI, именно с него логично начать рассмотрение ко-

да пользовательского

интерфейса. Подробности его реализации показаны

в листинге 10.33.

 

 

 

 

Листинг 10.33. Создание пользовательского интерфейса окна с вариантами

createSuggestionsDiv:

function{)

{

this.suggestionsDiv

= document.createElement("div");

// О Создать элемент div

 

this.suggestionsDiv.className -

this.options.suggestDivClassName;

// © Определить стиль элемента div

var divStyle =

this.suggestionsDiv.style;

// © Добавить стиль поведения

 

divStyle.position

=

'absolute1;

 

divStyle.zlndex

=

101;

 

divStyle.display

 

-

"none";

 

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

/ / О Вставить в документ

this.textlnput.parentNode.appendChild (this.suggestionsDiv);

Метод создания контейнера имеет четыре основных обязательства, перечисленных в листинге. Прежде всего он должен создать элемент div посредством API createElement () документа О. Далее он должен определить стиль элемента div согласно клиентской конфигурации ©. Напомним, что одно из технических требований заключалось в том, чтобы стили CSS каждого экземпляра компонента допускали индивидуальную настройку. Для этого атрибут className элемента div устанавливается согласно свойству suggestDivClassName объекта опций. Значение этого свойства по умолчанию было задано (в методе setOptions) равным suggestDiv. Таким образом, если пользователь не укажет явно иное значение свойства, мы получим значение по умолчанию. Данное решение удобно, поскольку оно позволяет клиенту использовать таблицу стилей по умолчанию с нашими именами классов для определения стилей всех экземпляров компонента TextSuggest, используемых в приложении. Кроме того, можно предоставлять и другие таблицы стилей (например, заказные или реализованные в продукте), переопределяющие стандартные имена стилей. Кроме того, значение параметра suggestDivClassName может переопределяться на самой странице, формируя стилевое оформление компонента в пределах страницы или экземпляра. Звучит достаточно гибко.

Существуют определенные аспекты стиля всплывающего окна, которые мы не можем менять и которые относятся к поведенческим стилям, поэтому определяем их стиль явно, используя атрибут style элемента ©. Обратите внимание на то, что любой стиль, установленный программно посредством атрибута style, переопределяет все, что задано с помощью атрибута CSS className (обычно для этого используется таблица стилей). Перечислим упомянутые аспекты.

Объявление position='absolute' (компонент должен управлять положением элемента div).

Объявление zlndex=10l (всплывающее окно должно располагаться поверх любого другого элемента страницы).

Объявление display="none" (всплывающее окно должно скрываться от пользователя до тех пор, пока его не активизирует ввод с клавиатуры).

Обратите внимание на то, что значение 101 переменной zlndex выбрано практически произвольно. Наконец, метод вводит элемент div в документ в качестве потомка текстового поля О. Родительский элемент в данном случае значения не имеет, поскольку div будет располагаться посредством абсолютного позиционирования.

Размещение всплывающего окна

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

нии всплывающего окна на экране требуется, чтобы оно располагалось срази под текстовым полем и выравнивалось по его левому краю. Метод positionSuggestionsDiv, выполняющий данные действия, приведен в листинге 10.34.

Листинг 10.34. Размещение всплывающего пользовательского интерфейса •

positionSuggestionsDiv: function!) {

var textPos = RicoUtil.toDocumentPosition{this.textlnput); var divStyle = this.suggestionsDiv.style;

divStyle.top = (textPos.y + this.textlnput.offsetHeight)

+

"px";

 

divStyle.left = textPos.x + "px";

 

if ( this.options.matchTextWidth )

 

divStyle.width

= (this.textlnput.offsetWidth —

 

 

this.padding()) + "px";

—h

 

-

 

 

 

 

Напомним, что в предыдущей версии сценария мы написали метод для расчета абсолютного положения текстового поля. В реструктуризированной версии мы будем полагаться на вспомогательный метод от Шсо — toDocumentPosition(). Все, что требуется, — использовать данный метод для получения опорной точки и выполнения соответствующих расчетов, помещающих всплывающее окно ниже текстового поля, выровненным по его левому краю. Затем мы проверяем существование конфигурационной опции matchTextWidth, и если ее значение равно true, то задаем ширину элемента div равной ширине введенного текста. Обратите внимание на то, что мы выровняли ширину с помощью значения заполнения. Это было необходимо, поскольку стиль элемента div может задаваться извне с помощью класса CSS. Мы не знаем, какие поля и границы пользователь приписал нашему компоненту, возможно, они нарушат наше визуальное выравнивание по ширине текстового поля. Поэтому напишем метод padding() (листинг 10.35), который вычислит величину заполнения и полей слева и справа, а затем вычтет их из общей ширины.

Листинг 10,35. Расчет заполнения[слева и справа

padding: function() { try{

var styleFunc = RicoUtil.getElementsComputedStyle; var lPad = styleFunc( this.suggestionsDiv,

"paddingLeft", "padding-left" );

var rPad = styleFunc( this.suggestionsDiv, "paddingRight", "padding-right" );

var lBorder = styleFunc( this.suggestionsDiv, "borderLeftwidth", "border-left-width" );

var rBorder = styleFunc( this.suggestionsDiv, "borderRightWidth", "border-right-width" ) ;

lPad = isNaN(lPad) ? 0 : lPad; rPad = isNaN(rPad) ? 0 : rPad;

lBorder = isNaN(lBorder) ? 0 : lBorder;

rBorder » isNaN(rBorder) ? 0 : rBorder; return parselnt(lPad) + parselnt(rPad) +

parselnt(lBorder) + parselnt(rBorder); }catch (e){ return 0; }

__K

 

я

Чтобы получить рассчитанный стиль элемента (действительное значение атрибута, вне зависимости от того, как оно было задано), придется немного потрудиться. Для решения этой задачи Internet Explorer предлагает собственный атрибут currentstyle для каждого элемента, браузеры Mozilla используют метод getComputedStyle () свойства defaultview документа. Все эти механизмы требуют также конкретной спецификации запрашиваемого атрибута. Атрибут currentstyle (Internet Explorer) ожидает, что внешний вид будет задан с помощью связывания в стиле JavaScript (например, borderRightWidth), тогда как getComputedStyle() (Mozilla) ожидает, что атрибуты будут заданы согласно синтаксису таблиц стилей (например, border-right- width). К счастью, библиотека Rico предлагает метод, решающий все эти проблемы, — RicoUtil.getElementsComputedStyle(). От нас требуется только передать ему элемент, имя атрибута Internet Explorer и имя атрибута Mozilla, а метод возвращает значение. В данном случае метод принимает величину границ и полей слева и справа, суммирует их и возвращает результат.

Метод Rico.getElementsComputedstyle() известен некорректной работой с некоторыми версиями Safari, поэтому мы вручную задаем возвращаемое значение по умолчанию в блоке t r y . . .catch.

Содержимое всплывающего окна

Итак, у нас есть код, создающий и размещающий всплывающее окно. Далее требуется написать метод, заполняющий это окно предлагаемыми вариантами. Напомним, что метод ajaxUpdateO разбирает ответный XML-документ, превращая его в массив объектов-предположений. Кроме того, если существует хотя бы одно предположение, вызывается метод this .updateSugggestionsDiv(). Данный метод преобразовывает внутреннее представление набора предлагаемых вариантов в реальные элементы span всплывающего окна (элемента div). Рассмотрим, как это происходит.

updateSuggestionsDiv:

function()

{

 

this.suggestionsDiv.innerHTML =

"";

//

Удалить

предыдущий

контекст

 

 

v a r s u g g e s t L i n e s •> t h i s , c r e a t e S u g g e s t i o n S p a n s ( ) ;

//

Создать

новый

контекст

 

 

for (var

i = 0;

i <

s u g g e s t L i n e s . l e n g t h ; i++)

t h i s . s u g g e s t i o n s D i v . a p p e n d C h i l d ( s u g g e s t L i n e s [ i ] ) ;

) ,

Данный метод обманчиво прост — на самом деле предстоит еще многое сделать. Указанный метод устанавливает значение свойства innerHTML элемента suggestionsDiv, созданного ранее в виде пустой строки (чтобы уничтожить все предыдущее содержимое). Затем вызывается функция createSuggestionSpans (), создающая блок span для каждого варианта в массиве

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

предложений. Наконец, созданные блоки последовательно обрабатываются и добавляются к элементу div. Вот здесь уже начинается реальная работа. Рассмотрим подробнее функцию createSuggestionSpans (), приведенную в листинге 10.36, и разберем, как создаются блоки-варианты.

Листинг 10,36. Создание позиций списка предлагаемых вариантов

createSuggestionSpans: function() {

 

 

var regExpFlags «• "";

 

 

if { this.options.ignoreCase )

 

 

regExpFlags - 'ir ;

 

 

var startRegExp = " ";

 

 

if { this.options.matchAnywhere )

 

 

StartRegExp = ' ';

 

 

var regExp new RegExp( startRegExp +

 

 

this.lastRequestString,

 

 

regExpFlags );

 

 

var suggestionSpans = [};

 

 

for ( var i = 0 ; i < this.suggestions.length ; i++ )

 

 

suggestionSpans.push(

 

 

this.createSuggestionSpan( i, regExp ) );

 

 

Ь return suggestionSpans;

m

Данный метод вначале изучает объект опций и находит значение свойств ignoreCase и matchAnywhere. Благодаря этому мы определим соответствующие параметры регулярного выражения, которое облегчит извлечение из ответного документа части строки, совпадающей с текстом, набранным пользователем. Затем метод последовательно обрабатывает свойство suggestions (массив объектов, имеющих свойства .text и .value). Для каждого предлагаемого варианта в массиве вызывается метод createSuggestionSpan() с номером предположения и созданным ранее регулярным выражением. Таким образом, всю основную работу выполняет метод createSuggestionSpan(), показанный в листинге 10.37.

Листинг 10.37. Создание блока с предлагаемым вариантом createSuggestionSpan: function( n, regExp ) {

var suggestion = this.suggestions[nj;

var suggestionSpan = document.createElement("span"); suggestionSpan.className = this.options.suggestionClassName; suggestionSpan. style, width =* '100%'; suggestionSpan.style.display = 'block1;

suggestionSpan. id = this.id + "__" + n; suggestionSpan.onmouseover =

this.mouseoverHandler.bindAsEventListener(this); suggestionSpan.onclick =

this.itemClickHandler.bindAsEventListener(this); var textValues - this.splitTextValues( suggestion.text,

this.lastRequestString.length, regExp );

var textMatchSpan = document.createElement("span"); textMatchSpan.id - this.id + "_match_" + n;

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

textMatchSpan.className = this.options.matchClassName; textMatchSpan.onmouseover =

this.mouseoverHandler.bindAsEventListener(this); textMatchSpan.onclick =

this.itemClickHandler.bindAsEventListener(this);

textMatchSpan.appendChild( document.createTextNode(textValues.mid) );

suggestionSpan.appendChild( document.createTextNode(textValues.start) );

suggestionSpan.appendChild(textMatchSpan);

suggestionSpan.appendChild( document.createTextNode(textValues.end) );

Ьreturn suggestionSpan;

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

<span>before <span> matching text

</span>, and after</span>

Мы сильно упростили реальную картину, чтобы проиллюстрировать структуру. Предположим, что пользователь ввел слова "matching text", а в базе данных имеется значение "before matching text, and after". Это значение будет предложено в качестве предполагаемого варианта, но, кроме того, с элементом span будут связаны дополнительные атрибуты, облегчающие идентификацию, стилевое оформление и обработку событий. Работу по отсечению фрагментов текста до и после соответствия выполняет следующая строка кода:

var textValues = this.splitTextValues(

suggestion.text, this.lastReguestString.length, regExp );

Возвращенное значение textValues представляет собой объект, имеющий три свойства: .start, .mid и .end. Таким образом, в приведенном примере textValues — это объект, который выглядит приблизительно так:

textValues = { start: 'before ', mid: 'matching text', end: ', and after1 };

Наконец, ниже показана реализация метода splitTextValues ().

splitTextValues: function{ text, len, regExp ) { var startPos = text.search(regExp);

var matchText = text.substring( startPos, startPos + len ); var startText = startPos == 0 ?

"" : text.substring(O, startPos); var endText = text.substring( startPos + len );

return { start: startText, mid: matchText, end: endText };

h

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

Рассмотрев структуру блока предлагаемого варианта, поговорим о важных атрибутах, сгенерированных для блока. Внешний и внутренний блоки создаются с именами классов CSS на основе значения свойств suggestionClassName и matchClassName объекта Options соответственно. Благодаря классам CSS полностью настраиваемым является не только suggestionsDiv но и вся внутренняя HTML-структура каждого предлагаемого варианта.

Из других важных атрибутов, генерируемых в блоке, стоит упомянуть идентификаторы, которые позволяют обработчикам событий извлекать нужные блоки. Далее в блоки необходимо поместить обработчик событий опmouseover; это позволит компоненту обновлять выбор, переводя его на позицию, над которой в настоящее время находится указатель мыши. Кроме того, с каждым предлагаемым вариантом необходимо соотнести обработчик событий onclick, чтобы после щелчка на строке предположения значение этой строки помещалось в текстовое поле. Реализация описанных обработчиков событий показана в листинге 10.38.

Листинг 10.38, Взаимодействие мыши с позицией списка

mouseoverHandler: function(e) {

var src = e.srcElement ? e.srcElement : e.target;

var index = parselnt (src. id. substring(src.id.lastIndexOf ('__')+l));

К this.updateSelection(index);

 

itemClickHandler: function(e) {

 

this.mouseoverHandler(e);

 

this.hideSuggestions() ;

 

this.textlnput.focus();

 

—±

Обработчик mouseoverHandler () просто находит цель события и вычленяет сгенерированный нами идентификатор, представляющий номер предлагаемого варианта. Затем можно использовать метод updateSelection(}, написанный на четвертый день, обновляя выбор и переводя его на позицию, над которой в текущее время находится указатель мыши.

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

Наконец, мы завершили создание всплывающего окна. Сосредоточимся теперь на невероятно простой задаче его показа и сокрытия.

Отображение и сокрытие всплывающего окна

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

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