WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 5 | 6 || 8 | 9 |   ...   | 11 |

«Оглавление I. НОВЫЙ ВЗГЛЯД НА WEB-ПРИЛОЖЕНИЕ 31 1. Каким должен быть Web-интерфейс 33 2. Знакомство с Ajax 63 3. Управление кодом Ajax 99 II. ОСНОВНЫЕ ПОДХОДЫ К РАЗРАБОТКЕ ПРИЛОЖЕНИЙ 145 4. Web-страница ...»

-- [ Страница 7 ] --

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. Опережающий ввод В качестве ссылки используется функция 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();

if(isOpera)objText.onkeypress = GiveOptions;

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

Наша функция AddHandler () получает ссылку на текстовое окно. Эта ссылка позволяет добавлять к элементу обработчики событий onkeyup и on blur. Обработчик событий 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, кото рая вызывается при нажатии клавиш в текстовом окне. Функция GiveOp tions(), приведенная в листинге 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 == && !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. Опережающий ввод как мы начнем детектирование клавиши со стрелками и , мы должны убедиться, что текущее активизированное текстовое окно совпадает с послед ним активизированным текстовым окном.

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

После этого мы снимаем фокус с текстового окна и выходим из функции.

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

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

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

400 Часть IV. Ajax в примерах 10.3.3. Обращение к серверу Объект xMLHttpRequest позволяет передавать текст из текстового окна на сервер и получать результаты с сервера. В данном случае мы отправляем данные на сервер, поскольку серверная страница, созданная в листинге 10. обращается к элементам, отправленным в форме. Как показано в листин ге 10.11, в объекте ContentLoader требуется задать положение страницы на сервере, функцию, вызываемую после завершения выполнения этого объекта а также параметры формы, которые будут отправлены форме.

Листинг 10.11. Функция Ajax, используемая для отправки данных на сервер \ function TypeAhead(xStrText){ var st rParams • "q=" + xStrText + "&where=" + theTextBox.obj.matchAnywhere;

var l oader l = new net. Cont ent Loader( theTextBox. obj. serverCode, Bui l dChoi ces, nul l, "POST", st rParams);

) ш При вызове функции TypeAhead() из функции GiveOptions () мы (для выполнения поиска) передаем ей текущее значение строки из текстового ок на. Нам требуется создать строку параметров, strParams, содержащую зна чение строки в текстовом окне, а также булево значение matchAnywhere. Оба названных элемента использовались в листинге 10.1 для получения резуль татов поиска. Затем мы начинаем загружать документ, вызывая Content Loader. Для этого в качестве двух первых параметров ContentLoader мы отправляем URL серверной страницы и функцию JavaScript, которая будет вызвана при возврате результатов. Третий параметр равен null, поскольку мы собираемся игнорировать все сообщения об ошибках. Благодаря этому поле с опережающим вводом будет выглядеть подобно обычному текстовому полю. Последние два параметра указывают ContentLoader поместить данные на сервер и отправить параметры формы, содержащиеся в строке strParams.

После возврата результатов с сервера вызывается функция Build Choicest), позволяющая завершить обработку данных на стороне клиен та. Разрабатывая код серверной части сценария, мы возвращали результаты в виде двухмерного массива JavaScript. Этот массив содержал пары "текст значение" предлагаемых вариантов выбора. Тем не менее в отклике мы име ем просто строку символов. Следовательно, нужно взять эту возвращенную строку и отформатировать ее как массив JavaScript. Соответствующая функ ция, обрабатывающая информацию, полученную от ContentLoader с помо щью метода eval(), приводится в листинге 10.12.

Листинг 10.12. Преобразование свойства responseTextB массив JavaScript function BuildChoices(){ var strText = this.req.responseText;

eval(strText);

BuildList(strLastValue);

bMadeRequest = false;

Глава 10. Опережающий ввод Свойство responseText возвращенного объекта запроса позволяет полу чать текст из запроса Ajax. Чтобы данную возвращенную строку можно было использовать в нашем коде JavaScript, необходим метод evalO, нужным об разом обрабатывающий строку, переданную ему в качестве аргумента. В дан ном случае этот метод распознает, что строка представляет собой объявление переменной для создания нового массива. Если бы мы просто записали строку на страницу, она не была бы доступна для выражения JavaScript. Как пра вило, разработчики не приветствуют использование метода eval {) из-за его известной медлительности. Однако в данном случае он позволяет отказаться от циклического прохода XML-документа на стороне клиента для получения значений. Теперь мы можем вызвать функцию BuildListО, форматирую щую и отображающую возвращенные результаты. Кроме того, мы устанав ливаем значение булевой переменной bMadeRequest равным false, сообщая оставшейся части сценария, что запрос к серверу завершен.

Создание окна результатов Использование JavaScript для обработки текущего документа обычно счи тается признаком DHTML. В данном примере мы принимаем двухмерный массив и превращаем его в строки текста на экране. Вернувшись к рис. 10.4, мы увидим список слов с подчеркнутыми фрагментами — частями текста, совпадающими с тем, что ввел в окне пользователь. В нашем приложении данные слова будут отображаться как элемент span.

Функция BuildList (), созданная в листинге 10.13. использует три функ ции: поиск слов по шаблону, установку положения окна и форматирование результатов с использованием подчеркивания.

Листинг 10.13. Форматирование результатов в формат отображения function BuildList(theText){ SetElementPosition(theTextBox);

// Установить положение элемента, var theMatches - MakeMatches(theText);

// Отформатировать соответствия theMatches = theMatches.join().replace{/\,/gi,"");

// Показать результаты if(theMatches.length > 0){ document.getElementByld("spanOutput").innerHTML = theMatches;

document.getElementByldf "OptionsList_0").className = "spanHighElement";

currentValueSelected = 0;

bNoResults = false;

} //He показывать варианты else{ currentValueSelected = -1;

bNoResults = true;

if{theTextBox.obj.showNoMatchMessage) document.getElementByld( "spanOutput").innerHTML = 402 Часть IV. Aj'ax в примерах "" + theTextBox. obj. noMa.tchinqDataMessa.ge + "";

else HideTheBox();

} _ J, Функция BuildListO, приведенная в листинге 10.13, принимает строку введенную пользователем, и форматирует результаты. Первое, что мы долж ны сделать, — это динамически разместить элемент span непосредственно под текстовым окном, в котором реализован опережающий ввод. Для это го мы вызываем функцию SetElementPositionO (подробнее об этом — ни же, в разделе "Динамическая установка положения элемента"). Расположив элемент span в нужном месте страницы, мы можем манипулировать мас сивом, отыскивая соответствия с использованием функций MakeMatches() (речь о ней пойдет в разделе "Использование регулярных выражений"). Эта функция возвращает массив, содержащий только информацию, согласующу юся с введенной пользователем. В отличие от большинства интерактивных приложений опережающего ввода мы не требуем обработки на сервере, а с по мощью JavaScript ограничиваем результаты на стороне клиента.

Функция MakeMatches () форматирует результаты и возвращает их в ви де массива. Затем мы превращаем этот массив в строку, используя метод join. Если длина строки больше 0, тогда мы можем отображать результа ты в виде списка, задав его свойство innerHTML. Затем мы выбираем первый элемент списка и устанавливаем его свойство className, чтобы выделить этот элемент.

Если возвращенный массив не содержит данных, мы отображаем сообще ние "до matches" ("нет соответствий"), если текстовое окно это позволяет. Мы знаем, что соответствий нет, поскольку проверяем, что значение currents electedValue установлено равным -1. Если никаких сообщение отображать не требуется, мы скрываем окно.

Итак, мы завершили выполнение функции BuildListO и теперь можем создавать все функции, которая она вызывает. Первой из них рассмотрим SetElementPosition().

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

Попытавшись сослаться на левую верхнюю точку текстового окна, мы по лучим в ответ неопределенную строку. Следовательно, для определения по Глава 10. Опережающий ввод дожения элемента нужно использовать возможности JavaScript — тогда мы сможем выровнять окно с вариантами выбора непосредственно под тексто вым окном. Динамическое размещение элемента списка с выравниванием его под текстовым окном представлено в листинге 10.14.

Листинг 10.14. Динамическое нахождение положения элемента function SetElementPosition(theTextBoxInt){ var selectedPosX - 0;

var selectedPosY = 0;

var theElement « theTextBoxInt;

if (!theElement) return;

var theElemHeight = theElement.offsetHeightr var theElemWidth = theElement.offsetWidth;

while(theElement != null)I selectedPosX += theElement.offsetLeft;

selectedPosX += theElement.offsetTop;

theElement = theElement.offsetParent;

} xPosElement • document.getElementById("spanOutput");

xPosElement.style.left = selectedPosX;

if(theTextBoxInt.obj.matchTextBoxWidth) xPosElement.style.width • theElemWidth;

xPosElement.style.top = selectedPosY + theElemHeight xPosElement.style.display = "block";

if(theTextBoxInt.obj.useTimeout){ xPosElement.onmouseout = StartTimeout;

xPosElement.onmouseover = EraseTimeout;

} else{ xPosElement.onmouseout • null;

xPosElement.onroouseover *= null;

} _} _ и В листинге 10.14 мы объявляем функцию SetElementPosition(), прини мающую один параметр: ссылку на объект текстового окна. Значения двух локальных переменных, selectedPosX и selectedPosY, устанавливаются рав ными 0. Эти две целочисленные переменные используются для расчета по ложения элемента. Ссылка на текстовое окно задается в другой локальной переменной. Для получения ширины и высоты текстового окна применяются свойства offsetHeight и offsetWidth.

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

Получив положение текстового окна, мы можем извлекать объектную ссылку списка, использованную для задания верхней левой точки раскры вающегося списка с предложениями. Затем мы изучаем объект obj тексто вого окна, проверяя, должно ли его свойство width согласовываться с шири ной текстового окна. Если соответствующее булево значение равно true, мы 404 Часть IV Ajax в примерах устанавливаем ширину списка. Если значение равно false, ширина задается равной значению, указанному в таблице стилей. Последнее, что от нас требу ется, — изменить параметр видимости списка, чтобы он больше не скрывался от пользователя. Для этого свойство display устанавливается равным block.

Подытожим: мы правильно разместили список и сделали его видимым пользователю. Теперь можно разрабатывать код, заполняющий список пред положениями.

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

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

••;

: Листинг 10.15. Ограничение результатов с помощью регулярных выражений. • var countForld = 0;

function MakeMatches(xCompareStr){ countForld = 0;

var matchArray = new Array();

var regExp = new RegExp(theTextBox.obj.regExAny + xCompareStr,theTextBox.obj.regExFlags);

:

for(i =0;

i

i++) { var theMatch = arrOptions[i][0].match(regExp);

if(theMatch){ matchArray[matchArray.length]= CreateUnderline(arrOptions[i][0], xCompareStr,i);

} m Мы создаем функцию MakeMatches (), которая принимает один параметр:

строку, введенную пользователем. Затем мы присваиваем переменной count Forld значение 0 и создаем локальную переменную массива matchArray. (Об ратите внимание на то, что countForld — это глобальная переменная. Это позволяет немного упростить структуру примера. Позже мы избавимся от этой переменной.) Суть данной функции заключается в создании регуляр ного выражения, находящего варианты, согласующиеся с тем, что вводит пользователь. Поскольку мы уже определили параметры для регулярного выражения, создавая код, приведенный в листинге 10.6, сейчас нам требуется сослаться на объект текстового окна. Далее мы добавим свойство regExAny, позволяющее согласовывать текст, начиная с начала или любого места стро Глава 10 Опережающий ввод $#• Свойство regExFlags позволяет определять, следует ли при поиске соот ветствий игнорировать регистр.

По завершении работы с регулярными выражениями мы последователь но проходим массив arrOptions, проверяя, действительно ли находящиеся в нем варианты согласуются с нашим регулярным выражением. Если да, то МЫ добавляем текст в массив matchArray после вызова функции CreateUn derline(), которая форматирует код, отображаемый на экране.

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

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

Листинг 10.16. Обработка строк средствами JavaScript var undeStart = "";

var undeEnd = "";

l var selectSpanStart = "

' class='spanNormalElement' onmouseover=•SetHighColor(this)' ";

var selectSpanEnd ="";

function CreateUnderline(xStr,xTextMatch,xVal){ selectSpanMid = "onclick='SetText(" + xVal + " ) ' " + "id='OptionsList_"+countForId+ "'theArrayNumber='"+ xVal +'">";

var regExp = new RegExp{theTextBox.obj.regExAny + xTextMatch,theTextBox.obj.regExFlags);

var aStart = xStr.search(regExp);

var matchedText = xStr.substring(aStart, aStart + xTextMatch.length);

countForId++;

return selectSpanStart + selectSpanMid + xStr.replace(regExp,undeStart + matchedText + undeEnd) + selectSpanEnd;

406 Часть IV. Ajax в примерах В листинге 10.16 определяются две переменные, которые содержат стро ки, используемые для привязки класса CSS к фрагменту текста, согласу ющемуся с заданной строкой. Благодаря этому можно легко определить :тиль нужного текста. Первая переменная, undestart, содержит открыва ощий дескриптор span;

вторая, undeEnd, — соответствующий закрываю ций дескриптор.

Следующие две переменные формируют контейнер для всей строки. Этот сонтейнер позволяет манипулировать цветом фона и определять, щелкнул ли юльзователь на ячейке. Чтобы визуально выделить ячейку, на которую на !еден указатель мыши, мы добавили в переменную selectSpanStart событие louseover. Переменная selectSpanEnd представляет собой закрывающий де криптор элемента span.

Функция CreateUnderline() вызывается функцией MakeMatches<), ко юруго мы написали чуть выше. MakeMatches () принимает три параметра:

троку, введенную пользователем, текст предлагаемого варианта, а также начение данной опции. Используя переданные данные, можно создать об •аботчик onclick и добавить к списку идентификатор. Обработчик событий nclick позволяет выбрать один из предложенных вариантов, а идентифи атор — использовать DOM для выбора варианта из списка.

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

[тобы определить, в каком месте строки находится соответствие, применяет ^ метод search. Найдя положение искомой строки, мы можем получить под гроку, в которой можно сохранить исходное форматирование. Далее значе не счетчика countForld увеличивается на 1, и мы возвращаем отформатиро шную строку, объединяя все созданные элементы span. Теперь текст отфор атирован, осталось дописать классы CSS, добавленные к элементам span.

Элементам span были присвоены имена классов CSS, поэтому от нас 3 требуется вручную редактировать код JavaScript, изменяя определенные юйства текста. Это позволяет подогнать текстовое окно с опережающим юдом под любую цветовую схему, изменив несколько правил CSS.

span.spanMatchText{ text-decoration: underline;

font-weight: bold;

} span.spanNormalElement{ background: #C0COCO;

} span.spanHighElement{ background: #000040;

color: white;

cursor: pointer;

} span.noMatchData{ font-weight: bold;

color: #0000FF;

} Напомним (см. рис. 10.4), что текст списка, согласующийся с текстом на, подчеркнут и выделен полужирным. Оба этих свойства заданы в пра ле CSS span.spanMatchText. Стиль блока по умолчанию (серый цвет фо ) представлен в правиле span. spanNormalElement. К выбранному элементу именяется правило CSS span.spanHighElement. На рисунке также видно, о фон имеет темно-серый цвет, а текст представлен белым. Кроме того, рсор был заменен указателем, поэтому пользователь знает, что вариант жно выбрать с помощью мыши. К любому из этих элементов можно доба гь и другие свойства: шрифт, размер, границу и т.д. Итак, мы сформулиро Глава 10 Опережающий ввод вали правила таблицы стилей и закончили работу по выводу результатов на экран. Осталось только реализовать обработку клавиш со стрелками и кла виши и создать таймер, который будет скрывать список вариантов в моменты бездействия.

Выделение опций ранее в этой главе мы показали, как отслеживать нажатия клавиш со стрел ками вверх и вниз, чтобы пользователь перемещал selectedlndex вверх-вниз по списку, не прибегая к помощи мыши. При нажатии клавиш со стрелками мы получали 1 ("сместить выбор вниз") или -1 ("сместить выбор вверх").

При перемещении выбора мы применяем правила классов CSS к элемен там span. Кроме того, мы создали глобальную переменную currentValue Selected, в которую помещаем номер текущей позиции. Функция MoveHigh light(), приведенная в листинге 10.17, предлагает более богатый пользова тельский интерфейс, поскольку взаимодействует и с мышью, и с клавиатурой.

: Листинг 10.17. Изменение имен классов CSS с помощью JavaScript function MoveHighlight(xDir){ if (currentValueSelected >«= 0){ newValue = parselnt(currentValueSelected) + parselnt{xDir);

if(newValue > -1 && newValue < countForld){ CurrentValueSelected = newValue;

SetHighColor (null);

} } function SetHighColor(theTextBox){ if(theTextBox){ currentValueSelected = theTextBox.id.slice(theTextBox.id.indexOf("_")+1, theTextBox.id.length);

} for{i = 0;

i < countForld;

i++){ document.getElementById('OptionsList_' + i).className = spanNormalElement';

} document. getElementByld ( ' OptionsList' + currentValueSelected).className = 'spanHighElement';

_} m Функция MoveHighlight t) позволяет пользователю использовать для вы бора клавиши со стрелками вверх и вниз. Эта функция принимает один па раметр, xDir, обозначающий направление, в котором должно сместиться вы деление. Прежде всего мы должны убедиться, что у нас есть варианты на выбор и можно получить новое значение. Далее мы проверяем, что это новое значение принадлежит разрешенному диапазону. При положительном ответе мы устанавливаем состояние currentValueSelected и переходим к следую щей функции, SetHighColor (), выделяя новую выбранную позицию.

408 Часть IV. Ajax в примерах Функция SetHighColor() вызывается двумя различными событиями: на жатием клавиш со стрелками и наведением указателя мыши на позицию (об работчик событий onmouseover). Эта функция предназначена для удаления подсветки с последней выбранной опции и добавления ее к новой выбранной позиции. Событие onmouseover (см. листинг 10.16) принимает в качестве ар гумента объект блока предложенных вариантов;

следовательно, мы должны отсечь идентификатор и получить порядковый номер блока. Требуемое зна чение передают клавиши со стрелками, поэтому нам не нужно ничего делать так как функция moveHighlight () уже установила currentValueSelected.

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

Установка выбранного значения Мы разрабатываем сценарий, который позволил бы пользователям, выбирая из предложенных вариантов, уменьшать объем работы, требуемой для запол нения поля формы. Для этого нам нужно взять номер выбранного пользова телем элемента и установить текст в текстовом окне и значение в скрытом текстовом поле. Чтобы поведение нашего списка вариантов было похоже на поведение элемента HTML select, мы разработали три функции, приведен ные в листинге 10.18.

Листинг 10.18. Обработка нажатия клавиш со стрелками и щелчков мышью function SetText(xVal){ theTextBox.value = arrOptions[xVal][0];

//set text value theTextBox.obj.hidden, value •» arrOptions [xVal] [1] ;

document.getElementByldf"spanOutput").style.display = "none";

currentValueSelected = -1;

//remove the selected index } function GrabHighlighted(){ if(currentValueSelected >= 0){ xVal = document.getElementByldf"OptionsList_" + currentValueSelected).getAttribute("theArrayNumber");

SetTextfxVal);

HideTheBoxO ;

} } function HideTheBoxf){ document.getElementByldf"spanOutput").style.display = "none";

currentValueSelected = -1;

EraseTimeout();

_J Глава 10 Опережающий ввод Функция GrabHighlighted() позволяет получить текст и значение вы бранной позиции. Итак, вначале нам нужно проверить, выбрал ли пользо ватель значение. Если да, тогда мы получаем порядковый номер массива arrOptions, в котором располагается текст. Для этого мы берем установ ленное ранее значение атрибута theArrayNumber, затем вызываем функцию SetText (), устанавливающую текст значения выбранной опции в соответ ствующие элементы формы.

Функция SetText () использует порядковый номер, переданный ей как параметр для индексирования массива arrOptions. Видимый пользовате лю текст устанавливается путем индексирования первого индекса массива.

Скрытый элемент формы получает номер второго элемента, записанного в массиве. После того как мы извлечем значения, мы удалим список вариан тов с экрана, вызвав функцию HideTheBox ().

Функция HideTheBox () позволяет удалять блок spanOutput из поля зре ния. Для этого мы обращаемся к блоку и устанавливаем его свойство style.display равным none. Для удаления выбранного индекса присваива ем переменной currentValueSelected значение -1. Все запущенные таймеры удаляются посредством функции EraseTimeout(), разработкой которой мы займемся в следующем разделе.

Использование таймеров JavaScript Этот раздел является последним, в которым фигурирует JavaScript. Написав его, мы завершим создание приложения опережающего ввода, и вы сможете немного отдохнуть от всего этого кода. Итак, таймер, приведенный в листин ге 10.19, является еще одним элементом, делающим интерфейс пользователя немного богаче. Он используется для того, чтобы укрыть блок выбора по сле определенного периода бездействия. В данном случае мы используем ме тод JavaScript setTimeout(), выполняющий выражение по прошествии опре деленного времени. Это время задается в миллисекундах и привязывается к объекту, созданному в листинге 10.6. Отметим, что функция setTimeout () будет вызвана только в том случае, если присвоить параметру объекта use Timeout значение true.

Листинг 10.19. Присоединение и удаление запланированных событий :

function EraseTimeout(){ clearTimeout(isTiming);

isTiming • false;

} function StartTimeout{){ isTiming = setTimeout("HideTheBox()", theTextBox.obj.theVisibleTime);

Функция StartTimeout () устанавливает таймер при выполнении функ ции. Для инициализации таймера мы присваиваем переменной isTiming зна чение setTimeout. Метод setTimeout должен вызвать функцию HideThe Box () по прошествии установленного времени, которое задается с помощью theVisibleTime.

410 Часть IV. Ajax в примерах Рис. 10.5. Ход проекта опережающего ввода Единственное, что мы еще должны сделать, — это удалить блокировку по времени. Для этого мы создаем функцию EraseTimeout (), использующую встроенную функцию JavaScript clearTimeout () и предотвращающую сраба тывание HideTheBox(). Значение булевой переменной isTiming устанавлива ется равным false.

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

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

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

Глава 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, не требуя никакого ее изменения. Допускается лишь простая модификация заголовка с целью внедрения линии поведения компонента 2 Компонент должен без дополнительных усилий поддерживать многократное использование на одной странице 3 Должна существовать возможность индивидуальной настройки каждого экземпляра компонента Под этим имеются в виду как аспекты поведения (например, учет регистра, поиск с любого места), так и стилевое оформление CSS 4 Компонент не должен вводить глобальные переменные. Компания пользуется сторонними библиотеками JavaScript, и глобальное пространство имен уже достаточно засорено. Использование любых глобальных имен (кроме названия самого компонента) строго запрещено 5 Компонент должен предоставлять разумные значения по умолчанию для всех конфигурационных опций 6 Компонент должен работать в Internet Explorer и Firefox 7 Для уменьшения работ по кодированию, требуемых для улучшения качества и надежности решения, компонент должен быть создан на основе структуры с открытым исходным кодом 8 Кстати, если получится, сделайте это до конца недели вания. Несмотря на то что мы проделали немалую работу, имеющийся сце нарий не удовлетворяет даже половине из них. Сценарий уже готов, поэтому о требовании 7 можно забыть: нам не нужно уменьшать объем работ. Очевид но, что требованию 8 сценарий также удовлетворяет (по той же причине). Он поддерживает различные браузеры, поэтому снимаем и требование 6. А вот что касается остального, то определенную работу проделать все же придется.

У нас есть всего неделя, поэтому начнем.

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

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

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

var TextSuggest = Class.create();

TextSuggest.prototype = { // Вызывается в процессе создания initialize: function( pi, p2, p3 ) { h };

В данном фрагменте кода создается то, что можно считать "классом" (хотя сам язык не поддерживает такой концепции), и определяется функция конструктор i ni ti al i ze (). Клиент компонента может создавать экземпляры класса с помощью приведенной ниже строки кода.

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

<аj ax-response> ... the rest of the XML response as normal...

more response elements if needed..

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

Вот и все. День получился довольно коротким. Некоторое время мы по тратили на изучение структур с открытым исходным кодом, на которые мы можем опираться, и разработали план внедрения их в требуемый компонент.

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

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

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

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

416 Часть IV. Ajax в примерах Листинг 10.21. HTML-разметка компонента TextSuggest

updateSuggestionsDiv: function() { this.suggestionsDiv.innerHTML = "";

// Удалить предыдущий контекст var suggestLi nes •> t hi s, cr eat eSugges t i onSpans ( ) ;

// Создать новый контекст for (var i = 0;

i < suggest Li nes. l engt h;

i++) t hi s. suggest i onsDi v. appendChi l d(suggest Li nes[ i ] );

), Данный метод обманчиво прост — на самом деле предстоит еще многое сделать. Указанный метод устанавливает значение свойства innerHTML эле мента suggestionsDiv, созданного ранее в виде пустой строки (чтобы уни чтожить все предыдущее содержимое). Затем вызывается функция create SuggestionSpans (), создающая блок span для каждого варианта в массиве 434 Часть IV. Ajax в примерах предложений. Наконец, созданные блоки последовательно обрабатываются и добавляются к элементу div. Вот здесь уже начинается реальная рабо та. Рассмотрим подробнее функцию createSuggestionSpans (), приведенную в листинге 10.36, и разберем, как создаются блоки-варианты.

Листинг 10,36. Создание позиций списка предлагаемых вариантов createSuggestionSpans: function() { var regExpFlags «• "";

if { this.options.ignoreCase ) r regExpFlags - 'i ;

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 = 'block ;

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 Опережающий ввод 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-разметку предлагаемого варианта, которая выглядит примерно следу ющим образом:

before matching text , and after Мы сильно упростили реальную картину, чтобы проиллюстрировать структуру. Предположим, что пользователь ввел слова "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 на основе значения свойств sugges tionClassName и 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() должен скрыть всплы вающее окно с предлагаемыми вариантами, вызывая метод hideSugges tions () и возвращая текстовое поле в фокус, чтобы пользователь мог про должать набор.

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

Отображение и сокрытие всплывающего окна Создав код, обрабатывающий все сложные детали создания всплывающего списка с предлагаемыми вариантами, необходимо создать и код, отобража ющий и скрывающий этот список. К счастью, это довольно прямолинейный Глава 10. Опережающий ввод процесс, знакомый любому эпизодическому разработчику 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, чтобы отобразить (посредством значения, равного пустой строке) и скрыть (посредством попе) всплывающее окно. Метод showSugges tions () выполняет дополнительную работу по размещению окна в нужном месте перед его отображением. Вот и все! Действительно все! Компонент го тов. Подведем итоги.

10.5.6. Итоги Да, это был достаточно сложный компонент с большим числом элементов.

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

допускает расширение;

не нагру жает сервер;

к тому же он ненавязчив, работает во всех браузерах, имеет простой API... Другими словами, он удовлетворяет всем требованиям, пере численным в табл. 10.2. Полностью исходный код компонента можно най ти на сайте http://www.manning.com/crane (http://www.dialektika.com/);

библиотеку Rico — на http://openrico.org/, а библиотеку Prototype — на сайте http: //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. Классический портал Все мы уже привыкли к классическим порталам (мы используем их много лет). Во многих внутренних сетях компаний они применяются для повышения производительности, поскольку все находится в одном месте. Классический Рис. 11.1. Портал Yahoo! отображает заказную информацию портал позволяет пользователю входить в систему и настраивать содержимое по своему усмотрению. Например, портал компании может содержать один набор настроек для агента по продажам, а другой — для программиста. Обо им сотрудникам может пригодиться окно с календарем проектов компании, но не обоим требуются графики продаж или сообщения об ошибках приложе ния. Ограничивая информацию, предоставляемую сотрудникам, мы повыша ем и безопасность, и производительность, поскольку им не приходится искать нужную информацию по всей внутренней сети компании.

Еще одним классическим порталом является Yahoo!. Заходя на Yahoo!, мы можем получать почту, менять настройки прогноза погоды, чтобы они соот ветствовали нашему региону, изменять внешний вид страницы и др. Как вид но на рис. 11.1, портал Yahoo! можно настроить под требования пользователя.

Для этого в Yahoo! реализован механизм технических страниц, на кото рых пользователь может менять необходимые данные. На одной из таких страниц можно выбрать родной город, чтобы отображаемый прогноз пого ды касался только этой местности. На рис. 11.1, например, задан Мэриленд (США). Хотя возможность подобной настройки хороша уже сама по себе, хо рошее впечатление пользователя можно усилить, применив Ajax так же. как сделала компания Amazon при создании портала A9.com.

Рис. 11.2. Портал A9.com с результатами поиска в Web информации об Эрике Паскарелло 11.1.2. Портал с богатым пользовательским интерфейсом Портал Ajax предлагает богатый пользовательский интерфейс, более дина мичный, чем у классического портала, и более удобный для пользователя. Мы можем добавлять новое содержимое и без лишних сложностей менять способ его представления. Прекрасным примером этого легкого взаимодействия яв ляется поисковый портал Amazon A9.com. Посмотрим, как он работает. На рис. 11.2 показан поиск информации об Эрике Паскарелло с единственным установленным флажком Web.

Теперь сузим результаты поиска. Нам требуется книга, написанная дан ным человеком, поэтому мы устанавливаем флажок Books. В правой сто роне страницы появляется панель Book Results. Как показано на рис 11.3, на экран выводятся результаты поиска книг Эрика Паскарелло без повторной обработки всей страницы на сервере.

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

Итак, ознакомившись с несколькими преимуществами портала Ajax, мы можем переходить к архитектуре портала, который планируем создать.

Рис. 11.3. Портал A9.com с результатами поиска и колонкой Book Resul t s 11.2. Создание портала с использованием Java Чтобы создать в высокой степени настраиваемый портал Ajax для большого числа пользователей, необходим код клиентской части сценария, код сервер ной части сценария и база данных. Сторона клиента обрабатывает взаимодей ствие пользователей с окнами — перетаскивание, отправку данных на сервер с помощью Ajax, Сервер, в свою очередь, обрабатывает сеансы пользовате лей, передачу данных клиенту и взаимодействие с базой данных. База данных в одной таблице содержит имена и пароли наших пользователей, а во вто рой — метаданные окна портала (положение, размер и содержимое). Выпол нение данного проекта будет состоять из большого числа этапов, поскольку он является очень динамическим. Чтобы начать данный проект, рассмотрим его структуру (рис. 11.4).

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

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

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

Рис. 11.4- Схема действии портала Ajax Пользователи входят на портал и управляют своими окнами. Изменения записываются автоматически в фоновом режиме рис. 115. Свойства таблицы users в SQL Squirrel — графической программе работы с базами данных Рис. 11.6. Содержимое таблицы user s 11.3.1. Таблица пользователя Первой таблицей, с которой мы сталкиваемся в базе данных, является табли ца users, содержащая три столбца В данном проекте мы будем использовать только необходимый минимум информации, но в зависимости от наших тре бований ее объем может увеличиваться. Три столбца таблицы, id, username и password, создаются с помощью стандартного SQL-выражения, приведен ного в листинге 11.1. На рис. 11.5 показана таблица в клиентском приложении базы данных SQL Squirrel (http://squirrel-sql.sourceforge.net).

Листинг 11.1. Схема таблицы users create table users( id int primary key unique not null, username varchar(50) not null, password varchar(50) not null >;

• После создания таблицы необходимо создать несколько пользователей.

В данном случае мы жестко кодируем имена пользователей и пароли. Как видно на рис. 11.6, в таблицу добавлены два пользователя с идентификаци онными номерами 1 и 2. Эти номера потребуются нам позже.

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

446 Часть IV. Ajax в примерах Последнее, что от нас требуется, — сопоставить с таблицей права досту па. В учетных записях пользователей, которые будут обращаться к таблице должны предоставляться права на чтение и запись. Не задав права доступа, мы получим ошибки при обработке запросов SQL.

Итак, у нас есть таблица users, и теперь можно писать код процесса регистрации, начиная с его серверной части.

11.3.2. Серверная часть кода регистрации: Java Код серверной части сценария портала Ajax по своей сути прост, но его раз работка потребует множества шагов, поскольку нужно реализовать довольно много функциональных возможностей. Сейчас мы сконцентрируем внимание на вопросах, касающихся регистрации.

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

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

>, ЛИСТИНГ 11.2. Logi nFi i ter. j ava: код серверной части процесса регистрации public class LoginFiiter implements Filter { public void initfFilterConfig config) throws ServletException { } public void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain) throws lOException, ServletException { boolean accept=false;

HttpSession session=( (HttpServletRequest)request).getSession();

User user=(User) (session.getAttribute("user"));

// О Проверить сеанс для объекта User if (user==null){ accept=login(request);

// © Запросить аутентификацию }else{ accept=true;

Глава 11. Улучшенный Web-портал Ajax // © Разрешить вход } if (accept){ filterChain.doFilter (request,response);

// О Отправить запрос дальше }else( Writer writer=response.getWriter();

writer.write...

(JSUtil.getLoginError()) ;

// © Вернуть код ошибки writer,flush() ;

writer.closet);

} } private boolean login(ServletRequest request){ // 0 Получить из запроса информацию о пользователе String user=request.getParameter("username");

String password=request.getParameter("password");

User userObj=findUser(user,password);

if (userObj!=null){ HttpSession session= ((HttpServletRequest)request).getSession(true) ;

// О Записать сеанс для будущего использования session.setAttribute("user",userObj);

} return (userObj!=null);

} private User findUser(String user, String password) { User userObj=null;

Connection conn=DBUtil.getConnection();

// © Сформировать выражение SQL try{ String sql="SELECT id FROM users WHERE username='" +user+"' AND password™'"+password+"'";

Statement stmt=conn.createStatement();

ResultSet rs=stmt.executeQuery(sql);

if (rs.next()){ // © Создать объект User int id=rs.getlnt("id");

userObj=new User(id,user);

} Icatch (SQLException sqlex){ } return userObj;

} public void destroyO { J > M В данном случае мы применяем фильтр, проверяющий, присутствует л* уже в сеансе объект User О. Если да, то мы его принимаем ©;

в противного случае производится аутентификация с использованием имени пользовател* и пароля, указанных в запросе ©. Если запрос принят, он передается сервле ту О;

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

public static String getLoginError() { StringBuffer jsBuf=new StringBuffer().append("document.getElementByldf 'spanProcessing )(BBSS)n").append(".innerHTML = ").append("'The Username and Password are invalid';

(BBSS)n");

return jsBuf.toStringf);

} За аутентификацию отвечает метод login (), приведенный в листин ге 11.2. Мы извлекаем из запроса имя пользователя и пароль ®, а затем вызываем функцию finduserf), обращающуюся к базе данных и извлека ющую соответствующую строку О. (Мы абстрагируемся от всех вопросов, связанных с базой данных;

нас интересует только объект DBUtil.) Если стро ка, соответствующая данным пользователя, найдена, функция возвращает объект User ©, который затем записывается в сеансе О и используется при следующих прохождениях через данный фильтр, — далее нам уже не понадо бится предоставлять в строке запроса имя пользователя и пароль, поскольку объект User уже будет присутствовать в сеансе.

Описанный подход обладает еще одной приятной особенностью — он об легчает выход пользователя из системы. Все, что нам нужно, — удалить объ ект User из сеанса.

Как показано в листинге 11.3, сам по себе объект User представляет структуру базы данных.

If' Листинг^ 13. User.java public class User { private int id=-l;

private String userName-null;

public User(int id, String userName) { super();

this.id = id;

this.userName = userName;

} public int getld() { return id;

} public String getUserNamef) { return userName;

} _J В данном объекте мы не будем записывать поле пароля. При работе с пор талом он нам не понадобится, кроме того, его использование в сеансе пред ставляет угрозу безопасности! Поэтому работу над процессом регистрации на стороне сервера можно считать законченной. Ничего необычного. Теперь пойдем дальше и посмотрим, как с этим кодом будет реагировать клиентская часть приложения.

Глава 11. Улучшенный Web-портал Ajax 11.3.3. Структура регистрации (клиентская часть) Клиентская часть кода регистрации состоит из двух частей. Первая — ви зуальная, которую пользователь может видеть и с которой может взаи модействовать. Данный HTML-код мы создадим динамически;

вы удиви тесь, насколько просто создать структуру с помощью элементов div, span и правил CSS.

Вторая часть сценария — код Ajax или JavaScript, отправляющий запрос на сервер и обрабатывающий данные. В данном случае мы собираемся ввести метод JavaScript eval (). Этот метод обрабатывает переданную ему строку как код JavaScript. Если строка содержит имя переменной, метод создает пе ременную. Если в качестве входного параметра eval () получает вызов функ ции, метод выполняет эту функцию. Метод eval() — довольно мощный, но его производительность может быть невысокой из-за сложности задейство ванных операций.

Структура HTML Как и в предыдущих главах, для формирования структуры страницы мы не будем использовать таблицу. Использование таблиц для создания структуры увеличивает время визуализации страницы, а поскольку мы используем Ajax, то хотелось бы, чтобы все элементы нашего сценария работали и реагировали как можно быстрее. Нам необходимо поместить текстовое окно, поле пароля и кнопку отправки в форме, которую мы можем отправить на сервер. Кроме того, нам потребуется элемент span, чтобы мы могли отобразить поступившее с сервера сообщение об ошибке, если имя пользователя или пароль окажется недействительным. Помещая всю форму в элементы div и span, мы фор матируем HTML-код, получая заголовок портала. В листинге 11.4 показана структура HTML-кода заголовка регистрации.

Листинг 11.4. HTML-структура регистрации :Ы Вначале мы добавляем форму О к HTML-документу. Эта форма обеспе чивает семантически значимый контейнер для текстовых окон. Кроме того, она предоставляет путь для отступления — аутентификации без использова ния Ajax, т.е. посредством обычной отправки формы. Мы создаем заголовок div ©, вмещающий в себя весь наш код. Затем добавляем элемент span ©, содержащий текстовое поле имени пользователя О или поле пароля 0, обра батывающий элемент span 0 и кнопку отправки ©.

С кнопкой, используемой для отправки данных на сервер, должен соотно ситься обработчик событий onclick. Этот обработчик инициализирует Ajax, вызывая функцию JavaScript LoginRequestt) (объясняется ниже, в разделе "Код регистрации (JavaScript)").

Чтобы завершить работу с заголовком, осталось добавить текст © портала и место для содержимого по умолчанию ©, которое будет отображаться при загрузке страницы. В элементе div с именем defaultContent можно отобра зить любое сообщение. В данном примере мы просто помещаем в него строку текста, но вообще можно добавлять ссылки, изображения, текст и все, что нам будет угодно. Далее мы записываем HTML-форму;

ее непрезентабельный внешний вид без применения к элементам правил CSS показан на рис. 11.7.

Чтобы украсить эту "пресную" структуру, с элементами нужно соотнести правила CSS. Поскольку мы присвоили элементам собственные идентифика торы, это упрощает процесс. Чтобы обратиться к идентификатору элемента, поместим перед ним знак #• Таблицу стилей можно добавить как внешний или встроенный файл, используя дескриптор