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

Рис. 10.3. Строка JavaScript, получаемая при тестировании на выходе серверной страницы

ментировали операторы запроса формы, заменив их жестко закодированными значениями.

Листинг 10.2. Фрагмент кода для тестирования

//string strQuery = Request.Form.Get("q")-ToString(); string strQuery = "a";

string strAny = "";

//if (Request.Form.Get("where").ToLower() =- "true") //{

strAny = "%"; _ //}

m

В данном коде запрограммирован поиск всех слов, содержащих букву "а". Следовательно, при его выполнении объявление массива JavaScript выглядит так, как показано на рис 10.3. Мы видим, что приведенная строка выглядит правильно. Таким образом, комментарии и строки с присвоением значений можно убирать и продолжать написание сценария опережающего ввода. Возможно, вам интересно, где же выполняется кэширование данных, возвращаемых с сервера. За это отвечает сторона клиента. Сервер вызывается только раз: когда вводится первый символ или когда число результатов больше или равно 15. Нет никакого смысла запрашивать данные, если мы получим подмножество данных, полученных ранее.

Таким образом, мы завершили создание серверной части кода и переходим к клиентской.

10.3, Структура клиентской части сценария

Каркас клиентской части сценария содержит объект Ajax, именуемый XMLHttpRequest, и много DHTML-кода. Начнем с создания текстовых окон.

10.3.1. HTML

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

пользователем из предложенных вариантов. Второе текстовое окно проем не дает отправить форму на дообработку на сервер при нажатии клавиши <Enter>. По умолчанию в форме с одним текстовым полем с клавишей <Enter> соотнесена отправка формы. Самый простой способ отказаться от такого поведения добавить на страницу второе текстовое поле. Если вы до. бавляете опережающий ввод на страницу, содержащую несколько элементов формы, дополнительное окно вам не требуется. Таким образом, мы приходи^ к общей структуре HTML-документа, показанной в листинге 10.3.

Листинг 10.3. Структура HTML-страницы с опережающим вводом

<form name="Forml" AUTOCOMPLETE="off" ID="Forml">

AutoComplete Text Box: <input type="text" name="txtUserInput" /> <input type="hidden" name="txtUserValue" lD="hiddenl" />

<input type="text" name="txtlgnore" style="display:none" /> </form>

В листинге 10.3 мы добавили форму с отключенным автоматическим заполнением. Нам пришлось так сделать, чтобы браузер не помещал значения в поле при первой загрузке страницы. Описанная возможность очень удобна, но в данном случае она нарушит нашу схему опережающего ввода. Обратите внимание на то, что приведенный фрагмент используется только в Internet Explorer и не дает встроенным раскрывающимся спискам автозаполнения взаимодействовать с нашими раскрывающимися списками DHTML. Другие браузеры данный атрибут проигнорируют. Кремле того, мы добавили текстовое окно txtuserinput, скрытый элемент txtUserValue и ложное текстовое окно txtlgnore. С окном txtlgnore, используемым для предотвращения автоматической отправки формы, также соотнесен стиль CSS. согласно которому это окно невидимо. Данный результат можно получить и другими способами, но предложенный нами вариант является самым простым и быстрым решением. Итак, мы добавили в форму текстовые поля и можем писать код JavaScript.

10.3.2. JavaScript

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

Наблюдение за действиями пользователя (вводом с помощью клавиатуры и мыши).

Отправка данных на сервер/получение данных с сервера.

Формирование HTML-содержимого, с которым может взаимодействовать пользователь.

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

Когда пользователь набирает букву, становится видимым скрытый элемент span с информацией, имеющей отношение к набранной букве. На

Рис. 10.4. Приложение опережающего ввода в действии

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

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

Добавление внешнего файла JavaScript (Ajax)

Чтобы добавить в наше приложение функциональные возможности Ajax, мы должны включить в дескриптор заголовка внешний файл JavaScript net.js (см. главу 3). Этот файл содержит объект ContentLoader, позволяющий инициировать запрос Ajax без всяких проверок if-else.

<script type="textjavascript" src="net.js"></script>

Чтобы присоединить внешний файл, мы добавляем дескриптор JavaScript и включаем атрибут src, задающий внешний файл. Ссылка на файл организовывается точно так же, как ссылка на изображение или файл CSS. Названный файл определяет, как отправлять информацию на сервер, скрывая весь зависящий от браузера код за удобным интерфейсным объектом. Таким образом, мы сможем отправлять и извлекать данные с сервера, не обновляя страницу. Подключив данный файл к нашему проекту, мы можем начинать разработку опережающего ввода.

Выходной элемент span

На рис. 10.4 показано серое окно, содержащее все доступные варианты. Эт0 окно представляет собой элемент span HTML, который динамически Быстра. ивается точно под текстовым окном. Вместо того чтобы добавлять на страницу элемент span всякий раз, когда нам потребуется использовать сценарий мы можем добавить его на страницу из сценария.

В листинге 10.4 мы создаем новый элемент span с помощью DOM при загрузке страницы (событие onload). Мы вставляем span в страницу HTML с идентификатором spanOutput и именем класса CSS span Text Dropdown. Затем к телу документа присоединяется новый дочерний элемент — span. Добавленная нами ссылка класса CSS позволяет так присваивать правила, чтобы мы могли динамически размещать элемент span на странице. Поскольку нам придется динамически изменять его положение на экране в зависимости от расположения текстового окна, в соответствующем классе CSS мы задаем абсолютное позиционирование.

Листинг 10.4. Код JavaScript, отвечающий за вывод списка на экран

window.onload = function(){

var elemSpan = document.createElement("span"); elemSpan.id « "spanOutput"; elemSpan.className = "spanTextDropdown"; document.body.appendChild(elemSpan);

_ }

Для динамического добавления элемента span на страницу используется обработчик событий onload. Благодаря этому нам не требуется вручную добавлять этот элемент на страницу г>ри каждомчиспользовании сценария. Для создания списка применяется метод DOM, именуемый createElement. Затем нам нужно присвоить нашему новому элементу span идентификатор ID и атрибут className. Создав эти атрибуты, мы можем добавить элемент на страницу. Теперь создадим класс CSS (листинг 10.5), который позволит динамически располагать элемент на странице.

Листинг 10.5. Класс CSS для раскрывающегося списка

span.spanTextDropdown{ position: absolute; top: Opx;

left: Opx; width: 150px; z-index: 101;

background-color: #C0COCO; border: lpx solid #000000; padding-left: 2px; overflow: visible; display: none;

— 2

-

Изначально элемент span располагается в произвольной точке экрана, заданной с помощью параметров top и left. Мы задаем ширину элемента

д0 умолчанию и устанавливаем значение свойства z-index, соответствующее самому верхнему слою страницы. Правило CSS также позволяет определять стиль фона и границу нашего списка, чтобы он выделялся на странице. Свойство display задается равным попе, поэтому наш элемент невидим пользователю в момент первоначальной загрузки страницы. Как только пользователь начинает вводить данные в поле с опережающим вводом, свойство display меняется, поэтому мы можем увидеть результаты.

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

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

Листинг 10.6. Создание индивидуального объекта

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

var props={ elem: xElem,

hidden: xHidden, serverCode: xserverCode,

regExFlags: { (xignoreCase) ? "i" : "" ), regExAny: ( (xmatchAnywhere) ? " " : " " ) , matchAnywhere: xmatchAnywhere, matchTextBoxWidth: xmatchTextBoxWidth, theVisibleTime: xtheVisibleTime, showNoMatchMessage: xshowNoMatchMessage, noMatchingDataMessage: xnoMatchingDataMessage, useTimeout: xuseTimeout

1;

AddHandler(xElem) ; return props;

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

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

Описанный набор параметров слишком велик, чтобы передавать его функции. Мы должны взять эти параметры и присвоить их нашему объекту. Для этого используем форму записи JavaScript Object Notation (JSON) которая подробно описана в приложении Б. Ключевое слово определяется перед двоеточием, затем следует значение. Трактовка параметров ignoreCase и matchAnywhere несколько сложнее. Вместо хранения булева значения мы записываем в свойство эквивалентное регулярное выражение. В данном случае мы используем символ i для обозначения "игнорировать регистр" и " — для отметки начала строки в регулярных выражениях. Выбор такого подхода объясняется тем, что нам проще задать параметры регулярных выражений, чем использовать оператор if при каждом вызове функций.

Последним действием нашей функции является связывание с текстовым окном обработчиков событий. В данном примере мы вызываем функцию, автоматически добавляющую обработчики событий. Код этой функции будет приведен чуть ниже, а пока вызовем функцию SetProperties() для создания нашего объекта. Код, приведенный в листинге 10.7, выполняется обработчиком события onload (загрузка страницы) и позволяет задавать свойства текстового окна.

Листинг 10.7. Инициализация сценария, . Щ ^ ^ ^ ^ ^ Ш Я

window.onload = function(){

 

var elemSpan = document.createElement("span");

elemSpan.id = "spanOutput";

 

elemSpan.className = "spanTextDropdown";

document.body.appendChild(elemSpan);

document.Forml.txtUserlnput.obj =

 

SetProperties(document.Forml.txtUserlnput,

document.Forml.txtUserValue,'typeAheadData.aspx',

true,true,true,true,"No matching

Data",false,null);

}

я

Обработчики событий должны быть заданы при загрузке страницы. Следовательно, мы должно присвоить их обработчику событий window.onload, который мы создали ранее для добавления нового элемента span. В данном тримере мы используем только одно текстовое окно с опережающим вводом. Цалее нам потребуется обратиться к элементу формы, с которым мы желаем связать возможности опережающего ввода, и добавить к нему новое свойство >bj. Вместо того чтобы использовать глобальные переменные, присвоим дангому свойству наш индивидуальный объект, чтобы для получения значений с нему можно было обращаться из сценария.

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

395

В качестве ссылки используется функция SetPropertiesO. Затем мы присваиваем все параметры, созданные в листинге 10.6. Здесь важно ответить, что мы ссылаемся на два элемента формы, созданных в листинге Ю.З, и вызываем серверную страницу typeAh.eadData.aspx, созданную в листинге 10.1. Теперь, когда обработчик onload инициализировал процесс, мы можем добавлять обработчики событий, которые вызывает функция SetPropertiesO-

Обработчики событий

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

Листинг 10.8. Добавление обработчиков событий, - • ;

var

isOpera=(navigator-userAgent.toLowerCase(). indexOf{"opera")!= -1) ;

 

function

AddHandler(objText){

 

objText.onkeyup = GiveOptions;

 

objText.onblur = function(){

 

if(this . obj . useTimeout)StartTimeout();

1

 

_}

if(isOpera)objText.onkeypress = GiveOptions;

 

m

Листинг 10.8 начинается с выражения, определяющего используемый браузер. Вообще, в данном примере мы будем несколько раз использовать детектирование браузера, поскольку Opera детектирует ввод с клавиатуры не так, как другие браузеры. Указанный способ позволяет проще всего выяснить, используется ли в качестве браузера Opera, но он является не самым надежным, поскольку может имитировать поведение других браузеров.

Наша функция AddHandler () получает ссылку на текстовое окно. Эта ссылка позволяет добавлять к элементу обработчики событий onkeyup и onblur. Обработчик событий onkeyup запускает функцию GiveOptions () при отпускании клавиши клавиатуры. Следовательно, когда пользователь набирает пятибуквенное слово, функция GiveOptions запускается пять раз при отпускании клавиш.

Обработчик событий onblur, который мы связали с текстовым окном, вызывает функцию StartTimeout () (она представлена в листинге 10.19) в тот момент, когда текстовое окно выходит из фокуса. Действия, вследствие которых окно может потерять статус находящегося в фокусе, включают щелчки на других частях экрана или нажатие клавиши <ТаЬ>.

Почему мы особо обратили внимание на детектирование браузера Opera? Дело в том, что он не так реагирует на обработчик событий onkeyup, как другие браузеры, — при срабатывании onkeyup Opera не показывает значе-

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

ние в текстовом окне, в которое в текущий момент вводится текст. Проо;ге.\щ решается добавлением обработчика событий onkeypress. Из приведенного кода видно, что мы проверили используемый браузер с помощью булевой переменной isOpera, а затем присвоили текстовому окну обработчик событий onkeypress. Благодаря этому обработчику событий Opera служит нашим целям так же, как и другие браузеры. Поскольку теперь мы можем детектировать то, что пользователь вводит с клавиатуры, мы можем определить какие действия требуются в функции GiveOptions ().

Обработка нажатия клавиш

Мы собираемся создать функцию GiveOptions (), которая вызывается при нажатии клавиш. Основных задач у этой функции две: определить действие в зависимости от нажатой клавиши и решить, требуется ли использовать Ajax для получения данных с сервера или можно задействовать уже имеющуюся информацию. Таким образом, функция GiveOptions () требуется для того же, что и кэширование данных, рассмотренное в разделе 10.1.1. Используя для обработки дополнительных нажатий клавиш клиентский код, мы уменьшаем потребление полосы пропускания приложением опережающего ввода. Чтобы реализовать кэш доступных вариантов, установим на стороне клиента несколько глобальных переменных. Список глобальных переменных, с которых мы начнем нашу работу, приводится в листинге 10.9.

Листинг 10.9. Глобальные переменные, используемые в проекте

var arrOptions = new Arrayf);

 

 

var strLastValue = "";

 

 

var bMadeRequest;

 

 

var theTextBox;

 

 

var objLastActive;

\.

 

var currentValueSelected = -1;

\

 

var bNoResults = false;

\

 

var isTiming = false;

\

_

Первая глобальная переменная — arrOptions — ссылается на массив, который содержит все доступные опции из запроса сервера. Следующая переменная — strLastValue — содержит последнюю строку, которая находилась в текстовом окне. Переменная bMadeRequest представляет собой булеву метку, благодаря которой мы узнаем, что запрос уже отправлен серверу (поэтому нам не нужно отправлять дополнительные запросы). Эта метка подходит для решения проблемы быстрого набора, поэтому нам не нужно вводить специальные таймеры, как в Google Suggest.

Переменная theTextBox будет содержать ссылку на текстовое окно, находящееся в фокусе, a objLastActive — ссылку на последнее активизированное текстовое окно. Таким образом определяется, требуется ли обновление набора данных при переключении пользователем текстовых окон. В нашем примере видимое текстовое окно всего одно, но если данное решение будет реализовано в окне с несколькими текстовыми окнами, нам потребуется знать, какое из них находится в фокусе. Следующая переменная. currentValueS-

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

greeted, действует подобно переменной selectedlndex списка. Если ее значение равно -1, не выбирается ничего. Последняя необходимая на текущий иомент переменная — булева bNoResults. Посредством этой переменной сообщается, что результатов нет, поэтому мы можем их не искать. Переменная ^sTiming позволяет определять, запущен ли таймер на странице. Этот таймер нужен для того, чтобы скрыть от пользователя предлагаемые варианты в период бездействия.

Возможно, вы не совсем поняли роли всех этих глобальных переменных, но. начав их использовать, вы разберетесь с ними лучше. Ссылаясь на эти глобальные переменные, мы можем создать функцию GiveOptionsO, которая вызывается при нажатии клавиш в текстовом окне. Функция GiveOptions(), приведенная в листинге 10.10, позволяет определять действие, которое пользователь произвел в текстовом окне.

Листинг 10.10. Код JavaScript, отвечающий за детектирование нажатия клавиш

//О Детектировать нажатие клавиши function GiveOptions(e){

var intKey « -1; if(window.event){

intKey = event.keyCode; theTextBox = event.srcElement;

}

else{

intKey = e.which; theTextBox = e.target;

}

// © Обновить таймер if(theTextBox.obj.useTimeout){

if(isTiming)EraseTimeout();

StartTimeout();

}

II © Проверить, существует пи текст if(theTextBox.value.length == 0

&& !isOpera){

arrOptions = new ArrayO; HideTheBox(); strLastValue = "";

return false;

}

// О Определить функциональные клавиши if(objLastActive == theTextBox)(

if(intKey — 13){ GrabHighlighted(); theTextBox.blur(); return false;

}

else if(intKey == 38){ MoveHighlight(-l); return false;

}

else if(intKey == 40){ MoveHighlight(l); return false;

// © Обработать действия, соотнесенные с нажатиеы клавиш if(objLastActive != theTextBox ||

theTextBox.value

.indexOf{strLastValue) != 0 || ((arrOptions,length==0 || arrOptions.length==15 )

SS IbNoResults) || (theTextBox.value.length

<= strLastValue.length)){ objLastActive = theTextBox; bMadeRequest = true TypeAhead(theTextBox.value)

}

else iff!bMadeRequest){ BuildList(theTextBox.value);

}

// ® Записать то, что ввел пользователь strLastValue = theTextBox.value;

} _

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

Функция GiveOptionsO объявляется с параметром е, что позволяет детектировать источник события. Прежде всего нам нужно объявить локальную переменную intKey, содержащую код нажатой пользователем клавиши О. Чтобы определить, какая клавиша нажата, необходимо узнать, какой метод нужен для работы выбранного пользователем браузера. Если поддерживается свойство window.event — мы имеем дело с Internet Explorer. Чтобы по-' лучить код клавиши, применяется свойство event. keyCode, а для получения объекта, соотнесенного с текстовым окном, — свойство event.srcElement. Для получения кода клавиши в других браузерах применяется параметр е.which, ссылка на объект текстового окна находится с помощью е.target.

Далее нужно проверить, использует ли текстовое окно таймер, согласно которому окно скрывается через некоторое время ©. Для этого необходимо обратиться к свойству obj текстового окна (мы создали его ранее) и булевой переменной useTimeout. Если таймер запущен, мы останавливаем его и перезапускаем, вызывая функции EraseTimeout() и StartTimeout() (мы напишем их в разделе "Использование таймеров JavaScript").

Затем мы проверяем, имеется ли что-либо в текстовом окне ©. Если окно пусто, вызываем функцию HideTheBoxO (разрабатывается в разделе "Установка выбранного значения"), устанавливающую значение strLastValue равным null, и возвращаем значение false, чтобы выйти из функции. Если текстовое окно содержит текст, мы продолжаем выполнять функцию. До того

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

как мы начнем детектирование клавиши со стрелками и <Enter>, мы должны убедиться, что текущее активизированное текстовое окно совпадает с последним активизированным текстовым окном.

Первой клавишей, за нажатием которой мы должны следить, является <Enter>. имеющая код 13 О. Нажатие клавиши <Enter> позволит нам захватить значение выбранной позиции раскрывающегося списка и поместить его Б видимое текстовое окно. Таким образом, мы вызываем функцию GrabHighlighted() (ее мы тоже напишем в разделе "Установка выбранного значения"). После этого мы снимаем фокус с текстового окна и выходим из функции.

Далее нам требуются клавиши со стрелками вверх и вниз, имеющие коды 38 и 40 соответственно. При нажатии клавиш со стрелками выделение позиции перемещается вверх-вниз по списку. Выделение, показанное на рис. 10.4, имеет вид темно-серой полоски. Нажав клавишу со стрелкой вниз, можно выделить следующую позицию списка. Подробности реализации этого процесса рассмотрены ниже, в разделе "Выделение позиции". Пока же мы просто отметим, что при нажатии клавиши со стрелкой вниз функции MoveHighlight () отправляется значение 1, а при нажатии клавиши со стрелкой вверх —'значение - 1 .

Если "особые" клавиши не нажимались, мы проверяем, требуется ли для получения значений обращение к серверу, или мы можем взять их из списка, полученного ранее ©. В этом месте мы снова используем реализованный в нашем сценарии механизм кэширования, ограничивая число обращений к серверу (а также нагрузку на сервер). Итак, с помощью различных проверок нам нужно выяснить, требуются ли новые результаты. Вначале мы определяем находится ли в фокусе в текущий момент окно, активизированное последним. Далее проверятся, что текст, в текущий момент набранный пользователем в текстовом окне, отличается от предыдущего только добавлением новых символов в конце. Если результатов для вывода на экран не найдено или наше множество результатов насчитывает не больше пятнадцати элементов, необходимо получить данные с сервера. Последняя проверка позволяет убедиться, что длина текущего значения больше предыдущего. Получать данные с сервера требуется только тогда, когда это покажет любая из проверок. В таком случае мы устанавливаем статус objLastActive для текущего текстового окна. Затем задается булево значение, показывающее, что был отправлен запрос (это нужно для того, чтобы мы не отправили несколько запросов), и вызывается функция TypeAhead(), отвечающая за захват значений.

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

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