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

Ajax в действии

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

Глава 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 () и возвращая текстовое поле в фокус, чтобы пользователь мог продолжать набор.

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

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

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

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

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

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

showSuggestions: function() {

var divStyle = this.suggestionsDiv.style; if ( divStyle.display == " )

return;

this.positionSuggestionsDiv();

//Разместить всплывающий список divStyle.display = ";

//Показать всплывающий список

>.

// Скрыть всплывающий список hideSuggestions: function() {

this.suggestionsDiv.style.display = ' none';

Ь

m

В приведенном коде мы манипулируем свойством style.display метода suggestionsDiv, чтобы отобразить (посредством значения, равного пустой строке) и скрыть (посредством попе) всплывающее окно. Метод showSuggestions () выполняет дополнительную работу по размещению окна в нужном месте перед его отображением. Вот и все! Действительно все! Компонент готов. Подведем итоги.

10.5.6. Итоги

Да, это был достаточно сложный компонент с большим числом элементов. Мы создали компонент с возможностью повторного использования, которым можно гордиться. Разработанный компонент TextSuggest обрабатывает большое число конфигурационных параметров; допускает расширение; не нагружает сервер; к тому же он ненавязчив, работает во всех браузерах, имеет простой API... Другими словами, он удовлетворяет всем требованиям, перечисленным в табл. 10.2. Полностью исходный код компонента можно найти на сайте http://www.manning.com/crane (http://www.dialektika.com/); библиотеку Rico — на http://openrico.org/, а библиотеку Prototype — на сайте h t t p : //prototype.conio.net/.

10.6. Резюме

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

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

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

Улучшенный

Вэтой главе.

Создание портала Ajax

Реализация каркаса регистрации

Создание динамических окон

Запоминание состояния окна

• Адаптация кода библиотеки .

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

Внастоящее время вес больше и больше компаний создают внутренние сети на основе порталов. Порталы предлагают пользователю удобный шлюз для получения больших объемов информации на одной странице Благодаря этому пользователю не требуется заходить на множество Web-сайтов, чтобы получить требуемую информацию. Интерактивные порталы, подобные Yahoo!, позволяют получать новости, прогнозы погоды, результаты спортивных соревнований, почту, игры и многое другое на одной странице. Другим примером портала является принадлежащий Amazon поисковый портал A9.com, позволяющий выполнять поиск во многих областях без переходов на отдельные страницы С его помощью на одной странице можно искать Web-страницы, книги, изображения и многое другое. В А9. com для отображения информации на экране используется Ajax. Это производит невероятно благоприятное впечатление, поскольку пользователю не требуется сидеть и ждать повторной визуализации страницы, когда будут отображены новые результаты поиска.

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

11.1. Эволюционирующий портал

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

11.1.1. Классический портал

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