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

Ajax в действии

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

Гпава 11. Улучшенный Web-портал Ajax 471

следней строки конструктора следует необходимость адаптации библиотеки AjaxWindows.js.

11.6.2. Адаптация библиотеки AjaxWindows.js

Напомним, что реализация данного портала использует внешнюю библиотеку AjaxWindows.js для создания отдельных окон портала и управления их размером и положением на экране. В связи с этим нам, в частности, требуется адаптировать библиотеку для отправки запросов Ajax менеджеру портала, чтобы записать настройки после события mouseup. Мы отслеживаем данное действие потому, что теоретически им заканчиваются все операции перемещения и изменения размеров. Первое, что мы сделали для выполнения данной адаптации, — скопировали код библиотеки AjaxWindows.js и изменили в нем фрагмент, помещающий в документ обработчик событий mouseup. Если рассматривать библиотеку AjaxWindow.js как продукт стороннего производителя, то недостатки данного подхода очевидны. Мы отошли от кода чужой библиотеки, т.е. модифицировали исходный код и поведение библиотеки так, что они перестали быть совместимыми с версиями, поддерживаемыми авторами библиотеки. Если библиотека изменится, нам придется согласовывать его со своими изменениями при выходе каждой следующей версии. Мы ничего не сделали, чтобы изолировать место изменения и сделать его как можно более "безболезненным". Поэтому рассмотрим менее радикальный подход к адаптации и выясним, можно ли как-то исправить данную ситуацию. Напомним, что последняя строка конструктора выглядела следующим образом:

this . initDocumentMouseHandler();

Метод initDocumentMouseHandler () представляет оперативную адаптацию библиотеки AjaxWindows.js. Он, как и ранее, просто перезаписывает обработчик document.onmouseup, но делает это уже в нашем собственном коде. Теперь логика, требуемая для адаптации внутри метода портала handleMouseUp(), реализована в нашем методе (листинг 11.14).

Листинг 11.14. Адаптация обработчика AjaxWindows.js

initDocumentMouseHandler: function() { var oThis = this;

document.onmouseup = function() { oThis.handleMouseUp(); };

Ь

handleMouseUp: function() { bDrag = false;

bResize = false; intLastX = -1;

document.body.style.cursor = "default"; if { elemWin && bHasMoved )

this.saveWindowProperties(elemWin.id); bHasMoved = false;

Ь

.

__Л

 

 

 

 

Это решение уже гораздо лучше, но это еще не все. Если библиотека AjaxWindows.js определяет обработчик mouseup в именованной, а не в ано-

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

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

function ajaxWindowsMouseUpHandler() { // logic here ...

}

document.onmouseup = ajaxWindowsMouseUpHandler;

Функция ajaxWindowsMouseUpHandler () представляет собой обратный вызов, определенный внешней библиотекой AjaxWindows.js. Как показано ниже, ее применение позволит сохранить определение метода и использовать его позже.

initDocumentMouseHandler: function() {

this.ajaxWindowsMouseUpHandler = aj axWindowsMouseUpHandler;

// О Сохранить

нашу собственную ссылку

v a r oThis =

t h i s ;

document.onmouseup = f u n c t i o n ( ) { oThis . handleMouseUp(); };

Ь

//© Вызвать библиотечную функцию handleMouseUp: function() {

this.aj axWindowsMouseUpHandler() ;

//© Добавить функциональные возможности Ajax if { elemWin && bHasMoved )

this.saveWindowProperties(eleraWin.id) ;

>,

Теперь наш метод handleMouseUp () не должен дублировать функциональные возможности библиотеки AjaxWindows.js. Необходимые возможности мы вызываем © посредством записанной ссылки О, а затем добавляем функциональные возможности Ajax ©. Если же обработчик mouseup библиотеки Ajax Windows в будущем изменится, то эти изменения не потребуют модификации нашего кода. Это уже более приятная ситуация с точки зрения управления изменениями. Разумеется, она предполагает, что подразумеваемый контракт с библиотекой не изменится — имеются в виду две глобальные переменные, elemWin и HasMoved. Поскольку в текущий момент библиотека определяет обработчик mouseup как анонимную функцию, мы по-прежнему можем записать ссылку на существующую функцию обработки события mouseup, используя следующую строку кода:

this.ajaxWindowsMouseUpHandler - this.document.onmouseup;

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

Пожалуй, это все, на что можно надеяться при адаптации библиотеки. Перейдем теперь к API портала. Изучая метод handleMouseUp (), можно догадаться об одной из трех команд портала, которые должен иметь компонент портала. При отпускании кнопки мыши вызывается метод saveWindowProp- e r t i e s t ) , записывающий состояние и положение текущего окна. Ниже мы подробно рассмотрим детали этого процесса, а также разберем другие API команд портала.

11.6.3. Задание команд портала

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

Итак, мы организовали в портале поддержку таких команд: регистрация, загрузка и запись настроек. Еще мы собираемся ввести возможность добавления и удаления окон, о которой мы уже упоминали, хотя и не показывали ее полную реализацию. Перечисленные возможности можно представлять как методы нашего портала. Однако, прежде чем мы начнем рассмотрение кода, выполним подготовительную работу, которая поможет нам в локализации изменений. Прежде всего, мы имеем в виду имена самих команд. Давайте определим символы для всех имен команд, чтобы их можно было использовать в любой части компонента. Рассмотрим следующий набор символов:

Portal.LOGIN_ACTION » "login"; Portal.LOAD__SETTINGS_ACTION - "PageLoad"; Portal.SAVE_SETTINGS_ACTION = "UpdateDragWindow"; Portal.ADD_WINDOW_ACTION - "AddWindow"; Portal.DELETE_WINDOW_ACTION - "DeleteWindow";

Хотя используемый язык и не поддерживает напрямую константы, предположим, что, используя договоренность о прописных буквах, можно считать указанные значения постоянными. Мы можем просто провести данные строковые литералы через наш код, но данный подход слишком неаккуратен. При подобном использовании констант наши "магические" строки будут располагаться в одном месте. Если контракт сервера изменится, мы сможем к этому приспособиться. Представим, например, каким образом может измениться контракт сервера (табл. 11.1).

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

myPortal.issuePortalCommand( Portal.SAVE_SETTINGS_ACTION, "settingl=" + settinglValue, "setting2=" + setting2Value, . . . )-"

В данном сценарии мы рассматриваем метод issuePortalCommand(), принимающий в качестве первого аргумента имя команды (например, одну из

Таблица 11.1. Изменения открытого контракта

Изменение контракта сервера

Требуемое действие

Переименована команда (например,

Заменить правую часть оператора присваивания

PageLoad заменена глагольной

константы LOAD_SETTINGS_ACTION новым

формой LoadPage)

значением. Остальная часть кода не меняется

Сервер больше не поддерживает

Удалить контракт и выполнить глобальный поиск

команду

всех упоминаний команды. В каждом случае

 

произвести необходимую модификацию кода

Сервер поддерживает новую команду

Добавить константу для этой команды

 

и использовать ее имя в коде

наших констант) и переменное число аргументов, соответствующих параметрам, которые ожидает/требует команда. Как и следовало ожидать, параметры имеют точно такую же форму, которая требуется методом sendRequest() объекта net.ContentLoader. Определенный нами метод issuePortalCommand() можно реализовать следующим образом:

issuePortalCommand: function{ commandName ) {

/ / О Получить параметр действия

v a r a c t i o n P a r a m - t h i s . o p t i o n s [ t q u o t e a c t i o n p a r a m t q u o t e ] ; // © Получить суффикс URL

v a r u r l S u f f i x = t h i s . o p t i o n s [ t q u o t e u r l S u f f i x t q u o t e J ; i f ( ' u r l S u f f i x ) u r l S u f f i x = " " ;

v a r u r l = t h i s . b a s e U r l ; var callParms •" [];

if (actionParam){ callParms.push(

//© Применить параметр действия actionParam + "=" + commandName ); }else{

//О Применить суффикс URL

url += "/" + commandName + urlSuffix; } for ( var i = 1 ; i < arguments.length ; i++ )

callParms.push( argumentsfi] ); var ajaxHelper = new

// 0 Создать объект ContentLoader net.ContentLoader( this, url, "POST", [] );

ajaxHelper.sendRequest // 0 Отправить запрос

,apply( ajaxHelper, callParms );

),

Данный метод создает URL, основываясь на конфигурационных опциях, обсуждавшихся в разделе 11.6.1, Если мы установили значение actionParam О, оно будет добавлено к параметрам с помощью POST, передаваемым на сервер ©. Если нет, мы добавим команду к пути URL О, присоединяя суффикс URL, если он указан в опциях ©. Первый аргумент функции — имя команды. Все остальные аргументы рассматриваются как параметры запроса. Затем сформированный URL передается объекту ContentLoader ©, и, как показано в предыдущем примере, отправляется запрос со всеми предоставленными

параметрами ©. Благодаря данному методу все API команд нашего портала будут очень лаконичными . Еще один "бонус" подобного общего метода заключается в том, что мы можем поддерживать новые команды, появляющиеся на сервере, не меняя код клиентской части приложения. А теперь рассмотрим команды, которые мы уже знаем.

регистрация

Напомним, что обработчик событий onclick нашей кнопки регистрации инициирует вызов метода login () страницы, который в свою очередь вызывает указанный выше метод. Функция login (по крайней мере, с точки зрения сервера) представляет собой команду, которую сервер должен обработать, проверив предоставленную пользователем регистрационную информацию, а затем (если эта информация верна) ответив так же, как команда load-page. Учитывая сказанное, рассмотрим реализацию функции login (), показанную в листинге 11.15.

login: function(userName, password) { this.userName = userName; this.password - password;

if ( this.options.messageSpanld ) document.getElementById(

this.options.messageSpanld).innerHTML = "Verifying Credentials";

 

this.issuePortalCommand( Portal.LOGIN_ACTION,

 

 

},

"user=" + this.userName, "pass=" + this.password

);

 

 

 

m

Данный метод помещает сообщение "Verifying Credentials" в элемент span, определяемый настраиваемой опцией t h i s . options .messageSpanld. Затем он отправляет команду login внутреннему коду портала, передавая регистрационную информацию, полученную методом в виде параметров запроса. Всю сложную работу выполняет метод issuePortalCommand{).

Загрузка настроек

Напомним, что функция createPortai () нашей страницы вызывает указанный выше метод для загрузки исходной конфигурации окон портала. Для загрузки настроек страницы применяется метод, который даже проще рассмотренного выше метода регистрации. Это просто удобная интерфейсная оболочка вокруг команды issuePortalCommand{). В качестве единственного своего параметра, используемого сервером для загрузки нужных настроек окон, она передает имя пользователя.

loadPage: function(action) {

this.issuePortalCommand( Portal.LOAD_SETTINGS_ACTION, "user=" + this.userName, "pass=" + this.password ); },

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

Запись настроек

Метод записи настроек также достаточно прост. Напомним, что данный метод вызывается адаптированной библиотекой AjaxWindows.js при наступлении события mouseup и производит запись всех операций перемещения и изменения размеров.

saveWindowProperties: function(id) {

this.issuePortalCommand( 'portal.SAVE_SETTINGS_ACTION, "ref=" + id, "x=" + parselnt(elemWin.style.left), "y=" + parselnt(elemWin.style.top),

»w=" + parselnt(elemWin.style.width),

"h=" + parselnt(elemWin.style.height) ); elemWin = null; },

Добавление и удаление окон

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

addWindow: f u n c t i o n ( t i t l e ,

u r l , х,

у,

w,

h)

{

 

 

 

t h i s . issuePortalCommand (

P o r t a l .ADD__WINDOW_ACTION,

" t i t l e - "

+

t i t l e ,

" u r l - "

+

u r l ,

"x="

+ x,

" y = " + y, " w = " + w, " h = " + h ) ; },

 

deleteWindow: f u n c t i o n ( i d )

{

 

 

 

 

 

 

 

 

v a r d o D e l e t e •

 

 

 

 

 

 

 

 

 

 

c o n f i r m ( " A r e you

s u r e

you

want

to

d e l e t e

t h i s

window?");

i f ( d o D e l e t e ) t h i s . i s s u e P o r t a l C o m m a n d (

 

 

 

 

Portal.DELETE_WINDOW_ACTION,

" r e f = " +

id

);

},

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

11.6.4. Обработке средствами Ajax

Как уже отмечалось, в данном примере мы используем технологию Ajax для обработки ответов. В частности здесь реализуется взаимодействие, ориентированное на сценарии. Описанная технология основана на том, что ожидаемый ответ сервера представляет собой приемлемый код JavaScript. При таком подходе очень желательно, чтобы клиенту для понимания ответа не требовалось выполнять какую-либо сортировку или синтаксический анализ. Ответ вычисляется с помощью метода JavaScript eval (), и в дальнейшем с клиента снимается вся ответственность. Недостаток данного подхода состоит Б ТОМ, что вся ответственность возлагается на сервер, который отвечает за понимание клиентской объектной модели и генерирует синтаксически верный ответ, согласующийся с требованиями языка (JavaScript). Второй недостаток описанного подхода частично устраняется с помощью использования для определения откликов популярной разновидности рассматриваемой технологии — JSON. Существуют определенные серверные библиотеки, помогающие гене-

рировать отклики JSON (см. главу 3), хотя они ближе к тому, что в главе 5 называлось подходом, ориентированным на данные.

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

ajaxUpdate: function(request){this.runScript(request.responseText);' runScript: function(scriptText){eval(scriptText);},

Как обсуждалось выше, обработка ответа слишком проста. Все, что мы делаем, — это вызываем метод runScript О с параметром responseText ("текст ответа"), и runScript () применяет функцию eval () к тексту отклика. У вас может возникнуть вопрос: "Почему бы вообще не избавиться от метода runScript () и просто вызывать eval() из метода aj axUpdate () ?" Да, это действительно допустимый и полезный подход. Тем не менее иногда удобно иметь метод, инкапсулирующий концепцию запуска сценария. Например, что будет, если мы добавим в реализацию runScript () этап предварительной или последующей обработки? Опять же мы изолировали место изменения. К счастью, метод a j axUpdate () не замечает изменения, и мы получаем новое поведение. Одной из интересных сфер применения описанной технологии может быть препроцессор, выполняющий замещение значений, перед выполнением функций, располагающихся на стороне клиента.

В завершение обсуждения обработки Ajax рассмотрим первостепенную по важности тему обработки ошибок. В связи с этим напомним, что метод handleError (), так же, как и метод ajaxUpdate(), является неявным контрактом, требуемым для сообщения с net. ContentLoader. Реализация метода handleError () приведена ниже.

handleError: function(request) {

if (this.options.messageSpanld) document.getElementById

( t h i s . o p t i o n s .messageSpanld) .innerHTML «•

"Oops! S e r v e r e r r o r . P l e a s e t r y a g a i n l a t e r . " ; } .

Данный метод проверяет существование конфигурационного свойства messageSpanld и при его наличии использует его в качестве элемента, отображая сообщение "Oops!" в пользовательском интерфейсе. Фактический текст сообщения также можно представить как параметр с помощью объекта опций. Сделать это предлагается читателям в качестве самостоятельного упражнения.

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

11.6.5. Выводы

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

сти портала на основе Ajax. Вряд ли какой-нибудь разработчик пожелает поместить систему-портал в угол своей страницы! Во-вторых, мы использовали технологию обработки откликов Ajax в виде кода JavaScript. При реструктуризации данного компонента мы старались изолировать точки изменения. Это было проиллюстрировано несколькими способами.

Мы создали понятный способ адаптации библиотеки AjaxWindows.js.

Изолировали строковые литералы как псевдоконстанты.

Написали общий метод генерации команд.

• Концепцию запуска ответного

сценария Ajax изолировали с помо-

щью метода.

 

 

11.7. Резюме

 

 

__

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

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

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

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

Вданной главе мы генерировали простые ответные XML-документы от сервера и вручную декодировали их с использованием JavaScript. В следую ющей главе мы рассмотрим альтернативный подход: использование таблиц стилей XSLT на стороне клиента для преобразования абстрактного XMLкода непосредственно в HTML-разметку.

Вэтой главе.

Технологии динамического поиска

Использование XSLT для трансляции XML в HTML

Закладки на динамическую информацию

Создание компонента '"живого" поиска

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

Инфраструктура Ajax позволяет сделать так, чтобы во время выполнения сервером трудоемких операций пользователь не терял контроль над тем, что происходит на стороне клиента. Если процесс обработки довольно длительный, пользователю можно предоставить анимированное GIF-изображение уведомляющее его о происходящем. Кроме того, во время выполнения серверного процесса пользователь сможет производить другие действия, при этом не думая, что браузер "завис".

В данной главе мы используем данную технологию Ajax для создания "живого" поиска. С помощью XSLT (Extensible Stylesheet Language Transformations — расширяемые преобразования языка таблиц стилей) мы будем преобразовывать XML-документ в HTML-структуру. Отметим, что трансляция с помощью XSLT удобнее разбора XML-кода вручную и создания HTML с использованием выражений JavaScript. В ней динамически генерируется XMLдокумент, которым мы заменяем код серверной части сценария и JavaScriptкод, составлявший основу предыдущих проектов. Таким образом, мы больше не будем вручную проверять, что все элементы HTML сформированы надлежащим образом.

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

сAjax и получите готовый компонент поиска, который сможете применять

всобственных проектах.

12.1.Понимание технологий поиска

Мы уже привыкли, что выполнение поиска на сервере "дополняется" демонстрацией застывшей страницы. (По крайней мере, на Web-сайтах, не располагающих кластером из 1 200 серверов, менее чем за секунду выполняющих поиск на 8 миллиардах страниц.) Чтобы убрать паузу, некоторые разработчики создают всплывающие окна и фреймы. Иногда для обработки используется дополнительное окно, что приветствуется пользователями, но также создает определенные проблемы. Используя Ajax, мы можем избежать использования фреймов и привычных задержек в классических операциях отправки форм.

12.1.1. Классический поиск

Рассмотрим классический процесс поиска. Включив форму поиска в Webсайт, мы предполагаем отправлять один или несколько элементов на сервер для дообработки. В качестве примера можно привести основную страницу поиска Google. Эта страница (www.google.com) содержит единственное текстовое окно и две кнопки поиска. В зависимости от выбранного действия форма либо перенаправит нас на список записей (с помощью которых мы можем переходить на требуемые страницы), либо переведет на страницу, соответствующую одной из позиций названного списка. Подобная структура прекрасно подходит для страницы, с которой не связаны никакие другие функциональные возможности, однако если поиск является частью большого