WWW.DISSERS.RU

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

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

, непосредственно включает его в таблицу. В противном случае генерируются необходимые де скрипторы
Pages:     | 1 |   ...   | 2 | 3 || 5 | 6 |   ...   | 11 |

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

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

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

Задача обмена данными между программами, составляющими приложе ние Ajax, не имеет аналогов в области классического программирования и, следовательно, изучена крайне мало. Мы попытаемся исправить положение дел. В первую очередь выделим четыре категории взаимодействия с пользо вателем: с участием только клиента, ориентированное на содержимое, ори Глава 5. Роль сервера в работе Ajax-приложения Ротированное на сценарий и ориентированное на данные. Взаимодействие, поддержке которого участвует лишь клиент, реализуется наиболее просто, мы вкратце обсудим его в следующем разделе, а затем на конкретном при зере подробно рассмотрим остальные три категории.

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

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

там же рассмотрены преимущества и недостатки каждого из них.

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

щелчком на пиктограмме, соответствующей планете, надо отобра зить эти сведения в окне (рис. 5.6). Сейчас мы не будем использовать объект Objectviewer, который рассматривался в главе 4, но еще вернемся к нему.

В данном случае нас больше всего интересует процесс доставки данных, которые должны быть отображены. Мы обсудим форматы данных, передава емых сервером, но не будем углубляться в детали их генерации, так как этот вопрос был рассмотрен в главе 3. В листинге 5.1 показана заготовка кли ентского кода приложения. На ее основе мы будем исследовать различные Механизмы доставки данных.

202 Часть II. Основные подходы к разработке приложений I Рис. 5.6. После щелчка на пиктограмме, соответствующей планете, информация о ней выводится в окне Листинг 5.1. Содержимое файла popups.html 4 Оглавление I. НОВЫЙ ВЗГЛЯД НА WEB-ПРИЛОЖЕНИЕ 31 1. Каким должен быть Web-интерфейс 33 2. Знакомство с Ajax 63 3. Управление кодом Ajax 99 II. ОСНОВНЫЕ ПОДХОДЫ К РАЗРАБОТКЕ ПРИЛОЖЕНИЙ 145 4. // О Включение библиотек JavaScript // 0 Включение пиктограмм, соответствующих планетам

mercury
venus
earth
mars
jupiter
saturn
uranus
neptune
pluto
В состав нашего файла мы включили несколько JavaScript-библиотек О.

Средства, предоставляемые net.j s, обеспечивают поддержку низкоуровне вых HTTP-запросов, используя для этой цели объект XMLHttpRequest, ко торый обсуждался в главе 2. В файле windows, js определен объект окна, допускающего перетаскивание. Это окно мы используем для отображения информации. Детали реализации окна в данном случае не имеют значения;

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

var MyWindow=new Window(bodyDiv,title,x,y, w,h);

где bodyDiv — это элемент DOM, который выводится в окне;

t i t l e — строка, отображаемая в заголовке окна, а параметры х, у, w и h определяют началь ные размеры окна. Задавая элемент DOM в качестве параметра, мы обеспе чиваем достаточную гибкость при отображении данных к окне. Полностью код объекта Window можно скопировать с Web-узла, посвященного данной книге. Посредством HTML-кода, содержащегося в файле, мы определяем эле мент div для каждой планеты ©, а в функции window.onload © связываем с пиктограммами планет обработчики onclick. В качестве обработчиков ис пользуются функции showlnf о (), которые в данном листинге не определены.

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

204 Часть II. Основные подходы к разработке приложений Клиент Рис. 5.7. Архитектура Ajax-приложения, ориентированная на содержимое. Клиент создает элемент IFrame и передает серверу запрос на получение информации. Содержимое генерируется моделью, представлением и контроллером уровня представления и возвращается элементу IFrame. На уровне клиента никакие требования к модели лредметной области не предъявляются 5.4.3. Взаимодействие, ориентированное на содержимое Наши первые шаги по использованию подхода Ajax напоминают действия, которые предпринимаются при создании классического Web-приложения.

В этом нет ничего удивительного;

как было сказано в главе 1, в названии первых велосипедов присутствовало слово "лошадь". Взаимодействие, ориен-:

тированное на содержимое, соответствует классическому подходу, но может иметь место и в Ajax-приложениях.

Общие сведения При взаимодействии, ориентированном на содержимое, HTML-данные гене!

рируются сервером и отображаются в составе элемента IFrame, включенно го на главную Web-страницу. Элементы IFrame обсуждались в главе 2. Их можно определить в HTML-коде страницы или сгенерировать с помощью программы. При программной генерации мы получаем динамический интер фейс, а окно браузера напоминает оконный диспетчер. На рис. 5.7 условно показана архитектура, ориентированная на содержимое.

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

Листинг 5.2. Содержимое файла ContentPopup. j s var offset=8;

function showlnfo(event){ var planet=this.id;

var infoWin=new ContentPopup( "info_"+planet+".html", planet+"Popup", Глава 5. Роль сервера в работе Ajax-приложения planet,offset,offset,320, );

offset+=32;

} function' ContentPopup(url,winEl,displayStr,x,y,w,h){ var bod=document.createElement("div") ;

document.body.appendChild(bod);

this.iframe=document.createElement("iframe");

this.iframe.className="winContents";

this.iframe.src=url;

bod.appendChild(this.iframe);

this.win=new windows.Window(bod,displayStr,x,y,w,h);

} _ Функция showlnf о () выполняет роль обработчика события для DOM элемента, представляющего планету. В обработчике ссылка this указывает на элемент DOM. Чтобы определить, для какой из планет отображаются данные, используется идентификатор элемента.

Мы определили объект ContentPopup, который генерирует универсаль ный объект Window, создает элемент I Frame, предназначенный для просмотра содержимого, и загружает в него ресурс, определяемый URL. В данной ситу ации мы лишь формируем имя статического HTML-файла и оформляем его в виде URL. В более сложном случае, если данные генерируются динамиче ски, нам, возможно, придется добавить к URL строку параметров. Простой файл, предназначенный для отображения в составе iFrame (листинг 5.3), ге нерируется сервером.

Листинг 5.3. Содержимое файла info_earth.html

earth
A small blue planet near the outer rim of the galaxy, third planet out from a middle-sized sun.

— В данном документе нет ничего примечательного: мы лишь используем обычный HTML-файл, как и для классического Web-приложения.

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

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

206 Часть II. Основные подходы к разработке приложений Он может оказаться полезен в двух случаях: при включении содержимого!

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

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

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

У некоторых разработчиков может возникнуть мысль использовать дан ный механизм для доставки динамических частей страницы, но в этом случае могут возникнуть проблемы при работе с элементами IFrame. Каждый эле мент I Frame поддерживает собственный контекст, и объем вспомогательно го кода, необходимого для обеспечения взаимодействия содержимого IFrame с родительским документом, может оказаться значительным. При взаимо действии со сценариями в других фреймах возникают еще более серьезные проблемы. Мы вернемся к этому вопросу при рассмотрении подхода, ориен тированного на сценарий.

Нельзя также забывать о проблемах, связанных с практичностью тради ционных Web-приложений. Во-первых, каждый запрос на получение содер жимого предполагает повторное получение статических данных. Во-вторых, несмотря на то, что при обновлении данных в основном документе автомати чески принимаются меры для подавления мерцания, в элементе IFrame этот эффект может присутствовать. Мерцания, правда, можно избежать, реали зовав дополнительный код для отображения сообщения "поверх" фрейма.

Итак, в качестве первого термина нашего словаря, описывающего запро сы к серверу Ajax, мы записали понятие "ориентированный на содержимое".

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

Глава 5. Роль сервера в работе Ajax-приложения Варианты применения подхода, ориентированного на содержимое До сих пор, рассматривая архитектуру, ориентированную на содержимое, мы предполагали, что для получения данных используется элемент IFrame. В ка честве альтернативного решения можно рассмотреть генерацию фрагмента HTML-кода, как реакцию на асинхронный запрос, и присвоение ответа свой ству innerHTML элемента DOM текущего документа. Более подробно мы рас смотрим данное решение в главе 12.

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

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

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

Для этой цели специально предусмотрены технологии RMI, Jini, и.NET Re moting Framework. Разработчики же простых Web-приложений решают по добные задачи постоянно! Логично предположить, что Ajax расширит наши возможности по работе с мобильным кодом.

Общие сведения В классическом Web-приложении HTML-код и JavaScript-сценарий обычно располагаются в одном файле, и сценарий специально ориентирован на ра боту с конкретной Web-страницей. Используя Ajax, мы можем загружать сценарии и Web-страницы независимо друг от друга, а это дает возмож ность модифицировать страницу различными способами, в зависимости от полученного сценария. Коды, составляющие клиентскую часть приложения, могут быть существенно расширены в процессе работы. Это не только предо ставляет новые возможности, но и, как вы вскоре увидите, создает проблемы.

На рис. 5.8 условно показана архитектура, ориентированная на сценарии.

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

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

В любом случае данная архитектура предполагает тесную связь между Уровнями. Для генерации кода на стороне сервера необходимо иметь инфор мацию об API на стороне клиента. При этом возникают две проблемы. Во первых, модифицируя клиентский или серверный код, можно случайно на 208 Часть II. Основные подходы к разработке приложений Рис. 5.8. Архитектура Ajax-приложения, ориентированная на сценарии.

Клиентская программа передает серверу запрос на получение фрагмента JavaScript-кода. Полученный код интерпретируется. На стороне клиента обеспечиваются точки входа для сгенерированных сценариев, что позволяет сценарию управлять клиентской программой рушить взаимодействие. Модульный подход к разработке и использование образа разработки Fagade могут несколько снизить подобную опасность. Во вторых, поток JavaScript-кода ориентирован на конкретного клиента, и ор ганизовать его повторное использование намного сложнее, чем, например, потока XML-данных. Следует, однако, заметить, что пригодность к повтор ному использованию не всегда бывает важна.

Теперь вернемся к примеру отображения информации о планетах Сол нечной системы. В листинге 5.4 показан простой API для отображения окон с данными.

Листинг 5.4. Функция showPopupO и вспомогательный код var offset=8;

function showPopup(name,description) { var win=new ScriptlframePopup (name,description,offset,offset,320,320);

offset+=32;

} function ScriptlframePopup(name,description,x,y,w,h){ var bod=document.createElement("div");

document.body.appendChild(bod);

this.contentDiv=document.createElement("div");

this.contentDiv.className="winContents";

this.contentDiv.innerHTML=description;

bod.appendChild(thi s.contentDiv);

this.win=new windows.Window(bod,name,x,y,w,h);

} _ Мы определили функцию showPopup (), которая получает два параметра и создает объект окна. В листинге 5.5 приведен пример сценария, вызываю щего эту функцию.

Глава 5. Роль сервера в работе Ajax-приложения Листинг 5.5. Содержимое файла script_earth. js var name='earth';

var description="A small blue planet near the outer rim of the galaxy," +"third planet out from a middle-sized sun.";

showPopup (name,description);

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

Загрузка сценариев в элементы IFrame Если мы загрузим JavaScript-код, используя HTML-дескриптор _ Если мы попытаемся загрузить этот код, он не будет работать, так как IFrame формирует собственный JavaScript-контекст и не может непосред ственно обратиться к API, определенному в основном документе. Если в сце нарии выполняется приведенное ниже выражение, браузер ищет функцию showPopup () в контексте IFrame.

showPopup (name, description);

210 Часть II. Основные подходы к разработке приложений В простых ситуациях, подобных рассматриваемой, при наличии двух кон текстов мы можем указать перед вызовом идентификатор top, формируя тем самым ссылку на документ верхнего уровня.

top.showPopup(name,description);

Если лее элемент I Frame содержится в составе другого элемента I Frame или если мы хотим запустить наше приложение в системе фреймов, ситуа ция усложняется.

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

var pinfo=new PlanetInfo(name,description);

Для того чтобы такое обращение было возможно, мы должны импорти ровать Planetlnfo. js в контекст IFrame, указав дополнительный дескрип тор Объект Planetlnfo, созданный в I frame, будет вести себя так же, как и объект, созданный во фрейме верхнего уровня, но они имеют разные про тотипы. Если впоследствии мы удалим элемент IFrame, а документ верхнего уровня сохранит ссылку на объект, созданный в составе IFrame, то после дующие обращения к методам объекта невозможны. Более того, оператор instanceof даст совершенно неожиданные результаты (табл. 5.1).

Импортирование определения объекта в различные контексты — не такая простая задача, как это может показаться на первый взгляд. Решить пробле му можно, определив в API документа верхнего уровня фабричный метод.

Например:

function createPlanetlnfo(name,description){ return new Planetlnfo(name,description);

} Глава 5. Роль сервера в работе Ajax-приложения При этом исчезает необходимость иметь ссылку на собственную версию объекта Planetlnfo.

функция showPopupO в листинге 5.4 представляет собой фабрику для объекта ScriptlframePopup.

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

Загрузка сценариев с использованием XMLHttpRequest и eval() Подобно многим языкам сценариев, JavaScript содержит функцию eval (), ко торая позволяет передавать интерпретатору JavaScript произвольный текст.

Некоторые считают, что использование функции eval () замедляет работу приложения. Это действительно так, если функция eval () систематически вызывается для малых сценариев. Однако, поскольку эта функция доступна нам, мы можем использовать ее для выполнения кода, загруженного с помо щью объекта XMLHttpRequest. Если функцию eval () вызывать относительно редко для сценариев большого объема, она обеспечивает приемлемую произ водительность.

Переписав наш пример для работы с функцией eval (), получим следую щий код:

function showlnfo(event){ var planet=this.id;

var scriptUrl="script_"+planet+".js";

new net.ContentLoader(scriptUrl, evalScript);

) function evalScript(){ var script=this.req.responseText;

eval(script);

} Теперь метод showlnfoO использует объект XMLHttpRequest (помещен ный в класс ContentLoader) для получения сценария с сервера;

причем необ ходимость включать его в состав HTML-страницы отпадает. Вторая функция, evalScript(), представляет собой функцию обратного вызова;

ссылка на нее передается ContentLoader. Когда функция evalScript () получает управле ние, может быть прочитано значение свойства responseText, принадлежа щего объекту XMLHttpRequest. Весь сценарий выполняется не в отдельном контексте IFrame, а в контексте текущей страницы.

Теперь мы описали новый термин — "ориентированный на сценарий". За метим, что данный подход допускает две реализации;

посредством IFrame или eval (). Сравним подход, ориентированный на сценарий, с подходом, ориен тированным на содержимое.

212 Часть II. Основные подходы к разработке приложений Проблемы и ограничения При непосредственной загрузке сценария с сервера общий объем сообщения уменьшается;

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

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

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

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

Общие сведения Иногда бывает необходимо организовать использование данных, полученных Ajax-клиентом, другими программами, например, интеллектуальными кли ентами Java или.NET, либо программным обеспечением мобильных телефо нов или КПК. В этих случаях JavaScript-инструкции плохо применимы;

более уместным был бы "нейтральный" формат данных.

При взаимодействии, ориентированном на данные, сервер передает лишь данные, которые обрабатываются не интерпретатором JavaScript, а самой клиентской программой. Архитектура, ориентированная на данные, услов но показана на рис. 5.9.

Большинство примеров, приведенных в данной книге, создано в рамках подхода, ориентированного на данные. Наиболее очевидный формат дан ных — XML, но возможны также и другие форматы.

Использование XML-данных В настоящее время формат XML применяется очень часто. Среда браузера, в которой выполняется Ajax-приложение и, в частности, объект XMLHttpRe quest, предоставляет средства поддержки XML-данных. Если объект XML HttpRequest получает ответ типа application/xml или text/xml, он оформ Глава 5. Роль сервера в работе Ajax-приложения Клиент Рис. 5.9. В системе, ориентированной на данные, сервер в ответ на запрос возвращает поток низкоуровневых данных (представленных, например, в формате XML). Эти данные обрабатываются на уровне клиента и используются для обновления клиентской модели и (или) пользовательского интерфейса ляет его в виде структуры DOM (Document Object Model). В листинге 5. приведен код приложения, отображающего данные о планетах, адаптирован ный для работы с информацией в формате XML.

Листинг 5.7. Содержимое файла DataXMLPopup. js |p var offset=8;

function showPopup(name,description){ var win=new DataPopup(name,description,offset,offset,320,320);

offset+=32;

} function DataPopup(name,description, x, y,w,h){ var bod=document.createElement("div");

document.body.appendChild(bod);

this.contentDiv=document.createElement("div");

this.contentDiv.className="winContents";

this.contentDiv.innerHTML=description;

bod.appendChild(this.contentDiv);

this.win=new windows.Window(bod,name,x,y,w,h);

} function showlnfo(event){ var planet=this.id;

var scriptUrl=planet+".xml";

new net.ContentLoader(scriptUrl,parseXML);

} function parseXML(){ var name="";

var descrip="";

var xmlDoc=this.req.responseXML;

var elDocRoot=xmlDoc.getElementsByTagName("planet")[0];

if (elDocRoot){ attrs=elDocRoot.attributes;

214 Часть II. Основные подходы к разработке приложений name=attrs.getNamedItem("name").value;

var ptype=attrs.getNamedItem("type").value;

if (ptype){ descrip+="

"+ptype+"

";

} descrip+="

    ";

    for(var i=0;

    i

    i++){ elChild=elDocRoot.childNodes[i];

    if (elChild.nodeName=="info"){ descrip+="

  • "+elChild.firstChild.data+"
  • (BBSS)n";

    } } descrip+="

";

}else{ alert("no document");

} top.showPopup(name,descrip);

} Функция showinfo() обращается к объекту XMLHttpRequest, содержаще муся в составе ContentLoader, и объявляет в качестве функции обратного вызова parseXML (). В данном случае функция обратного вызова сложнее, чем evalScript(), которую мы рассматривали в разделе 5.6.3. Это связа но с тем, что нам необходимо поддерживать навигацию по структуре DOM, извлекать данные и вызывать метод showPopup. В листинге 5.8 содержится пример ответа, сгенерированного сервером в формате XML.

Листинг 5.8. Содержимое файла earth.xml Earth is a small planet, third from the sun Surface coverage of water is roughly two-thirds Exhibits a remarkable diversity of climates and landscapes _ Существенным преимуществом XML является тот факт, что информа ция, представленная в этом формате, естественным образом структурирова на. Так, в данном примере содержится ряд дескрипторов , которые функция parseXML () преобразует в маркированный HTML-список.

Глава 5. Роль сервера в работе Ajax-приложения Таким образом, благодаря использованию XML нам удалось разделить уровни сервера и клиента. Код клиента и сервера можно изменять, независи мо один от другого, при условии, конечно, что они будут поддерживать фор мат документа. Однако подход, ориентированный на сценарий, имел очень важное преимущество: вся работа выполнялась JavaScript-интерпретатором.

Рассмотрим решение, предполагающее использование JSON. Оно позволяет объединить преимущества обеих архитектур.

Использование JSON-данных Имя для объекта XMLHttpRequest было выбрано не совсем удачно. На са мом деле он может принимать не только XML, но и любую текстовую ин формацию. Для передачи данных Ajax-клиенту очень удобен формат JSON (JavaScript Object Notation), так как он позволяет представить в компактном виде граф объектов JavaScript. В листинге 5.9 показано, как молено адапти ровать пример приложения, предоставляющего информацию о планетах, для использования JSON.

Листинг 5.9. Содержимое файла DataJSONPopup. j s function showlnfo(event) { var planet=this.id;

var scriptUrl=planet+".json";

new net.ContentLoader(scriptUrl,parseJSON);

} function parseJSON(), { var name="";

var descrip="";

var jsonTxt=net.req.responseText;

var jsonObj=eval("("+jsonTxt+")");

name=j sonObj.planet.name var ptype=jsonObj.planet.type;

if (ptypeH descrip+="

"+ptype+"

";

) var infos=jsonObj.planet.info;

descrip+="

    ";

    for(var i in infos){ descrip+="

  • "+infos[i]+"
  • \n";

    } descrip+="

";

top.showPopup(name,descrip);

-J • Здесь мы также загружаем данные с помощью ContentLoader. Функцией обратного вызова в этом случае является parse JSON (). Текст ответа представ ляет собой JavaScript-выражение, поэтому мы можем создать граф объектов путем вызова eval ().

216 Часть II. Основные подходы к разработке приложений var jsonObj=eval("("+jsonTxt+")") ;

Заметьте, что перед обработкой выражения нам надо поместить его в скобки. Теперь мы можем обращаться к свойствам объекта по имени, а это позволяет сократить размеры кода и сделать его более удобным для воспри ятия, чем методы обработки структуры DOM, которые мы использовали при работе с XML. Здесь метод showPopup() не приводится, поскольку он выгля дит точно так же, как и в листинге 5.7.

Какой же вид имеют JSON-данные? В листинге 5.10 показана информа ция о планете Земля, представленная как строка JSON.

Листинг 5.10. Содержимое файла earth, json { "planet":

{ "name": "earth", "type": "small", "info":

[ "Earth is a small planet, third from the sun", "Surface coverage of water is roughly two-thirds", "Exhibits a remarkable diversity of climates and landscapes" ] } _J С помощью фигурных скобок определяются ассоциативные массивы, а квадратные скобки обозначают массивы с числовыми индексами. Допус кается вложенность любых видов скобок. В приведенном примере мы опре делили объект planet, содержащий три свойства. Свойства name и type пред ставляют собой обычные строки, а свойство info является массивом.

Формат JSON используется реже, чем XML, но JSON-данные могут быть обработаны любым интерпретатором JavaScript, включая Mozilla Rhino на базе Java и Microsoft JScript.NET. Библиотеки JSON-RPC содержат средства разбора JSON для различных языков программирования (соот ветствующие ссылки приведены в конце данной главы), а также инстру мент JavaScript "Stringiner", предназначенный для преобразования JavaScript объектов в строки JSON. Таким образом, JSON можно рассматривать как формат для двустороннего взаимодействия. Если интерпретатор JavaScript доступен и на стороне клиента, и на стороне сервера, целесообразно выбрать формат JSON. В рамках проекта JSON-RPC были также разработаны биб лиотеки, предназначенные для разбора и генерации JSON-сообщений и ори ентированные на языки, которые часто используются при разработке про грамм, выполняемых на стороне сервера.

Теперь в нашем словаре появился термин "ориентированный на данные".

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

Глава 5. Роль сервера в работе Ajax-приложения Использование XSLT Альтернативой написанию программ для обработки дерева DOM и создания flTML-данных (этот подход был описан в разделе 5.7.3) является исполь зование XSLT-преобразования для автоматического конвертирования XML информации в формат XHTML. Этот подход представляет собой нечто сред нее между взаимодействием, ориентированным на данные, и взаимодействи ем ориентированным на содержимое. С точки зрения сервера эта архитек тура ориентирована на данные, а с точки зрения клиента — на содержимое.

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

Проблемы и ограничения Главный недостаток подхода, ориентированного на данные, состоит в том, что основная нагрузка, связанная с разбором информации, ложится на клиент скую программу. Это неизбежно ведет к усложнению кода на уровне клиента.

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

Три рассмотренных нами подхода описывают различные варианты си стем: от классического Web-приложения до "толстого" клиента, характерно го для настольных систем. К счастью, эти три архитектуры не являются взаимоисключающими и могут быть совместно использованы в рамках одно го приложения.

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

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

Рассмотрим сначала обновление с учетом изменений, которые мы вносим сами. Существуют два механизма передачи данных на сервер: HTML-формы и объект XMLHttpRequest. Рассмотрим кратко каждый из них.

218 Часть II. Основные подходы к разработке приложений 5.5.1. Использование HTML-форм В классическом Web-приложении элементы HTML-форм реализуют стан дартный механизм ввода данных пользователем. Элементы формы объяв ляются с помощью средств разметки HTML.

В результате обработки данного фрагмента кода отображаются два пу стых поля редактирования. Если пользователь введет в полях значения dave и letmein и активизирует кнопку submit, будет сформирован HTTP-запрос POST и передан ресурсу myFormHandlerURL.php. В теле запроса будет содер жаться текст username=dave&password=letmein. В большинстве систем Web программирования разработчику непосредственно не доступны закодирован ные данные формы. Вместо этого ему предоставляются пары имя-значения в виде ассоциативного массива или специальных переменных.

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

В этом случае функция, выполняющая проверку, будет иметь следую щий вид:

function validateForm(){ var form=document.getElementById('myForm') ;

var user=form.elements[0].value;

var pwd=form.elements[1].value;

if (user && user.length>0 && pwd && pwd.length>0){ form.action='myFormHandlerURL.php';

form.submit();

} else { al ert("pl ease f i l l in your credential s before logging i n" ) ;

} } Первоначально форма определяется без атрибута action. Обращение по реальному URL будет произведено только в случае положительного результа та проверки. С помощью JavaScript-кода можно также модифицировать фор му, организовав блокирование кнопки submit для предотвращения повторной передачи- данных, кодирование пароля перед формированием запроса и т.д.

Эти и многие другие приемы многократно описаны в литературе, поэтому Глава 5. Роль сервера в работе Ajax-приложения ^ibi не будем рассматривать их здесь. В главах 9 и 10 приведены примеры расширения возможностей HTML-форм.

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

Листинг 5.11. Функция submitData() function addParam(form,key,value) { var input=document.createElement("input");

input.name=key;

input.value=value;

form.appendChild(input);

} function submitData(url,data) { var form=document.createElement("form");

form.action=ur1;

form.method="POST";

for (var i in data){ addParam(form,i,data[i]);

} form.style.display="none";

document.body.appendChild(form);

form.submit();

_} Функция submitData () создает форму и в цикле включает в нее данные, используя для этого функцию addParam (). Обращение к функции submitDa ta () имеет следующий вид:

submitData( "myFormHandlerURL.php", {username:"dave",password:"letmein"} );

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

5.5.2. Использование объекта XMLHttpRequest Объект XMLHttpRequest уже рассматривался нами в этой главе, а также в гла ве 2. С точки зрения клиентского кода разница между чтением и обновлени ем незначительна. Нам надо лишь указать метод POST и передать парамет Ры формы.

220 Часть II. Основные подходы к разработке приложений В листинге 5.12 показан код объекта ContentLoader, который мы раз-Я работали в разделе 3.1. Мы несколько изменили его для того, чтобы можнпИ было включать в запрос параметры и указывать произвольный HTTP-метод Листинг 5.12. Объект ContentLoader //О Для конструктора предусмотрены дополнительные параметры net.ContentLoader=function (url,onload,onerror, method,params,contentType) this.onload=onload;

this.onerror=(onerror) ? onerror :

this.defaultError;

this.loadXMLDoc(url,method,params,contentType);

} net.ContentLoader.prototype.loadXMLDoc =function(ur1,method,params,contentType){ if (!method){ method="GET";

if (icontentType && method=="POST"){ contentType="application/x-www-form-urlencoded";

if (window.XMLHttpRequest){ this.req=new XMLHttpRequest();

else if (window.ActiveXObject){ this.req=new ActiveXObject("Microsoft.XMLHTTP");

if (this.req){ try{ this.req.onreadystatechange=net.ContentLoader.onReadyState;

// НТТР-метод this.req.open( method,url,true);

// Тип содержимого if (contentType){ this.req.setRequestHeader("Content-Type", contentType);

// Параметры запроса this.req.send( params);

}catch (err){ this.onerror.call(this) ;

} _ •. ;

Глава 5. Роль сервера в работе Ajax-приложения Конструктору объекта мы передаем несколько новых параметров О.

Из них обязательными являются URL (в соответствии с атрибутом action формы) и обработчик onload. Можно также задать HTTP-метод, парамет ры и тип запроса. Заметьте, что при передаче пар ключ-значение посред ством метода POST надо указать тип содержимого application/x-www-f orm urlencoded. Если при вызове функции тип явно не задан, мы устанавлива ем это значение автоматически. HTTP-метод используется при вызове мето да open () объекта XMLHttpRequest а параметры — при обращении к методу send( ). Таким образом, вызов конструктора имеет следующий вид:

var loader=net.ContentLoader( 'myFormHandlerURL.php', showResponse, null, ' POST', 'username=dave&password=letmein' );

В результате выполнения этого выражения будет сформирован такой же запрос, как и при использовании метода submitData(), представленного в ли стинге 5.11. Заметьте, что параметры передаются в виде строки, которая ко дируется так же, как и при передаче данных формы, например:

name=daveSjob=book&work=Ajax_In+Action Так действуют основные механизмы передачи данных серверу. Они могут активизироваться в результате различных событий: ввода пользователем тек ста, перемещения мыши, перетаскивания объекта и т.д. В следующем разделе мы вернемся к объекту Objectviewer, рассмотренному в главе 4, и выясним, как можно управлять обновлением модели предметной области.

5.5.3. Управление обновлением модели В главе 3 мы рассмотрели универсальный объект Objectviewer, предназна ченный для представления сложных моделей, и обсудили простой пример, в котором данный объект используется для просмотра информации о пла нетах. Каждый из объектов, представляющих планеты Солнечной системы, содержит несколько параметров, а некоторые текстовые свойства — диаметр и расстояние от Солнца — мы определили как редактируемые. Изменение любого свойства перехватывается центральной функцией обработки собы тий, которую мы использовали для представления отладочной информации в строке состояния браузера. (Возможность записывать данные в строку со стояния в последних версиях Mozilla Firefox ограничена. В приложении А мы рассмотрим простую консоль, поддерживаемую средствами JavaScript, кото рая позволяет отображать сообщения о состоянии системы при отсутствии в браузере строки состояния.) Средства обработки событий почти идеально подходят для передачи серверу информации об обновлениях.

222 Часть II. Основные подходы к разработке приложений Предположим, что на сервере выполняется сценарий updateDomain Model. jsp, который получает от клиента следующую информацию.

• Уникальный идентификатор планеты, информация о которой подлежит обновлению.

• Имя обновляемого свойства.

• Значение, присвоенное свойству.

Мы можем написать обработчик событий, который передавал бы серверу требуемую информацию.

function updateServer(propviewer) { var planetObj=propviewer.viewer.object;

var planetId=planetObj.id;

var propName=propviewer.name;

var val=propviewer.value;

net.ContentLoader ( 'updateDomainModel.jsp', someResponseHandler, null, 'POST', 'planetId='+encodeURI(planetId) +'&propertyName='+encodeURI(propName) +'&value='+encodeURI(val) );

} Этот обработчик надо связать с ObjectViewer.

myObjectViewer.addChangeListener(updateServer) ;

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

Простая очередь для хранения информации об обновлениях, реализованная средствами JavaScript, показана в листинге 5.13.

ЛИСТИНГ 5.13. Объект CommandQueue / / О Создать обьехт очереди net.CommandQueue=function(id, url, freq) { t hi s. i d=i d;

net.cmdQueues[id]=this;

this.url=url;

this.queued=new Array();

this.sent=new Array();

if (freq) { this.repeat(freq);

} } Глава 5. Роль сервера в работе Ajax-приложения // 0 Передать запрос серверу net.CommandQueue.prototype.addCommand=function(command){ if (this.isCommand(command)) { this.queue.append(command,true);

} } net.CommandQueue.prototype.fireRequest=function(){ if (this.queued.length==0) { return;

} var data="data=";

for(var i=0;

i

i++){ var cmd=this.queuedfi];

if (this.isCommand(cmd)){ data+=cmd.toRequestString();

this.sent[cmd.id]=cmd;

} } this.queued=new ArrayO;

this.loader=new net.ContentLoader( this.url, net.CommandQueue.onload,net.CommandQueue.onerro:

"POST",data );

} // © Проверить тип объекта net.CommandQueue.prototype.isCommand=function(obj) { return ( obj.implementsPropC'id") && obj.implementsFunc("toRequestString") && obj.implementsFunc("parseResponse") );

} // О Выполнить разбор ответа сервера net.CommandQueue.onload=function(loader) { var xmlDoc=net.req.responseXML;

var elDocRoot=xmlDoc.getElementsByTagName("commands")[0];

if (elDocRoot) { for(i=0;

KelDocRoot.childNodes.length;

i++) { elChild=elDocRoot.childNodes[i];

if (elChild.nodeName=="command") 224 Часть II. Основные подходы к разработке приложений { var attrs=elChild.attributes;

var id=attrs.getNamedItem("id").value;

var command=net.commandQueue.sent[id];

if (command) { command.parseResponse(elChild);

} } } } } net.CommandQueue.onerror=function(loader){ alert("problem sending the data to the server");

} // © Опрос сервера net.CommandQueue.prototype.repeat=function(freq){ this.unrepeat();

if (freq>0){ this.freq=freq;

var cmd="net.cmdQueues["+this.id+"].fireRequest()";

this.repeater=setlnterval(cmd,freq*1000) ;

} } // 0 Отключить опрос сервера net.CommandQueue.prototype.unrepeat=function() { if (this.repeater) { clearlnterval(this.repeater) ;

} this.repeater=null;

При инициализации О объекта CommandQueue (он назван так потому, что в очереди содержатся объекты Command) указывается уникальный иденти фикатор, URL сценария на стороне сервера и в качестве необязательного параметра — флаг, указывающий на необходимость повторного опроса. Алы тернативный вариант — активизация опроса вручную. Каждый из этих ре жимов может быть полезен, поэтому оба они предусмотрены в коде объекта.

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

В составе объекта содержатся два массива. Массив queued предполагает указание числовых индексов;

в него помещаются новые обновления. Массив sent — ассоциативный. Он содержит те обновления, которые были отправ лены серверу, но ответы на которые еще не были получены. В обоих масси вах содержатся объекты Command, интерфейс которых определяется функци ей isCommand() ©. Эти объекты обладают следующими свойствами.

Глава 5. Роль сервера в работе Ajax-приложения • Объект может иметь уникальный идентификатор.

• Объект допускает сериализацию для включения в состав запроса POST ©.

• Объект может выполнять разбор ответа сервера О и определять, успеш но ли был обработан запрос и должны ли предприниматься дальней шие действия.

Для проверки выполнения условий используется функция implements Func(). Поскольку данный метод принадлежит базовому классу Object, мо жет показаться, что он является стандартным инструментом JavaScript, но на самом деле мы объявляем функцию implements Func О в составе вспомо гательной библиотеки.

Obj ect.prototype.implementsFunc=function(funcName){ return this[funcName] && this[funcName] instanceof Function;

} Прототипы JavaScript подробно описаны в приложении Б. Теперь вернем ся к объекту, реализующему очередь. Метод onload очереди О ожидает отве та сервера, содержащего XML-документ. В составе документа должны при сутствовать дескрипторы , помещенные в дескриптор .

Методы repeat () © и unrepeat () © используются для управления объ ектом таймера при периодическом опросе сервера.

Объект Command, предназначенный для обновления свойств объекта, опи сывающего планету, показан в листинге 5.14.

Листинг 5.14. Объект UpdatePropertyCommand planets.commands.UpdatePropertyCommand=function(owner,field,value) { this.id=this.owner.id+"_"+field;

this.obj=owner;

this.field-field;

this.value=value;

} planets.commands.UpdatePropertyCommand.toRequestString= function() { return { type:"updateProperty", id:this.id, planetld:this.owner.id, field:this.field, value:this.value }.simpleXmlifу("command");

} 226 Часть II. Основные подходы к разработке приложений planets.commands.UpdatePropertyCommand.parseResponse= function(docEl) { var attrs=docEl.attributes;

var status=attrs.getNamedItem("status").value;

if (status!="ok") { var reason=attrs.getNamedItem("message").value;

al ert ("f ai l ed to update " +thi s. fi el d+" to "+this.value + "\n\n"+reason);

} } Данный объект предоставляет уникальный идентификатор команды и ин капсулирует параметры, необходимые серверу. Функция toRequeststring() оформляет сама себя в виде фрагмента XML-кода, используя специальную функцию, которую мы присоединили к прототипу Object.

Object.prototype.simpleXmlify=function(tagname){ var xml="<"+tagname;

for (i in this){ if (!this[i] instanceof Function) { xml+=" "+i+"=\""+this[i]+"\"";

} } xml+="/>";

return xml;

) В результате создается простой XML-дескриптор (для удобства восприя тия он отформатирован вручную).

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

В составе запроса POST, передаваемого серверу, может содержаться один или несколько описанных выше дескрипторов, в зависимости от частоты опроса и активности пользователя. Сервер обрабатывает каждую команду и сохраняет результаты, формируя ответ. Обработчик onload, принадлежа щий CommandQueue, распознает дескрипторы в ответе, сравнивает их с объ ектами Command в массиве sent и вызывает метод parseResponse () объекта Command. Ответ может выглядеть следующим образом:

Глава 5. Роль сервера в работе Ajax-приложения < / cornmands > Как видно из сообщения, информация о диаметре Меркурия была об новлена, но другие два обновления отклонены. Причины описаны посред ством атрибута message. Пользователь оповещается о возникающих пробле мах (обычно для этого используется функция al ert ()) и может предпринять ответные действия.

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

Листинг 5.15. Содержимое файла CommandServlet.Java public class CommandServlet extends HttpServlet { private Map commandTypes=null;

public void init() throws ServletException { ServletConfig config=getServletConfig();

// О Конфигурация обработчиков commandTypes=new HashMap();

boolean more=true;

for(i nt counter=l;

more;

counter++){ String typeName=config.getlnitParameter("type"+counter);

String typelmpl=config.getlnitParameter("impl"+counter);

if (typeName==null || typelmpl==null){ more=false;

} else { try{ Class cls=Class.forName(typelmpl);

commandTypes.put(typeName,els);

} catch (ClassNotFoundException clanfex) { thi s. l og( "couldn't resolve handler class name"+typelmpl);

} } } } 228 Часть II. Основные подходы к разработке приложений protected void doPost ( HttpServletRequest req, HttpServletResponse resp ) throws IOException{ // 0 Обработать запрос resp.setContentType("text/xml");

Reader reader=req.getReader();

Writer writer=resp.getWriter() ;

try{ SAXBuilder builder=new SAXBuilder(false);

// © Обработать XML-данные Document doc=builder.build(reader);

Element root=doc.getRootElement();

if ("commands".equals(root.getName())){ for(Iterator iter=root.getChildren("command").iterator();

iter.hasNext();

) { Element el=(Element)(iter.next());

String type=el.getAttributeValue("type");

XMLCommandProcessor command=getCommand(type,writer);

if (command!=null) { // О Делегировать обработку Element result=command.processXML(el);

writer.write(result.toString());

} } } else { sendError(writer, "incorrect document format - " +"expected top-level command tag");

} } catch (JDOMException jdomex){ sendError(writer,"unable to parse request document");

} } private XMLCommandProcessor getCommand (String type,Writer writer) throws IOException{ // © Соответствие обработчика команде XMLCommandProcessor cmd=null;

Class cls=(Class)(commandTypes.get(type));

if (cls!=null){ Глава 5. Роль сервера в работе Ajax-приложения try{ cmd=(XMLCommandProcessor)(els.newlnstance()) ;

}catch (ClassCastException castex){ sendError(writer, "class "+cls.getName() +" is not a command");

} catch (InstantiationException instex) { sendError(writer, "not able to create class "+cls.getName());

} catch (IllegalAccessException illex) { sendError(writer, "not allowed to create class "+cls.getName());

} }else{ sendError(writer,"no command type registered for "+type);

} return cmd;

} pr i vat e voi d sendError (Writer wr i t er, St r i ng message) throws IOException{ wr i t er. wr i t e( " ") ;

wr i t er. f l us h( ) ;

} _} — Данный сервлет поддерживает карту объектов XMLCommandProcessor, для конфигурирования которой используется интерфейс ServletConfig О. При использовании более мощных базовых средств можно воспользоваться для этой цели конфигурационным XML-файлом. При обработке запроса POST © для разбора XML-данных используется JDOM ©, а затем осуществляется перебор дескрипторов , для которых атрибуты соответствуют объ екту обработчика XMLCommandProcessor О. В карте содержатся определения классов, на основе которых в методе getCommand () мы создаем конкретные экземпляры, используя механизм отражения ©.

В интерфейсе XMLCommandProcessor объявлен единственный метод.

public interface XMLCommandProcessor { Element processXML(Element el);

} В данном интерфейсе предполагается, что для представления XML Данных будут применяться библиотеки JDOM. Объектами Element являются как параметры, так и возвращаемое значение. Простой класс, реализующий Данный интерфейс и предназначенный для обновления данных о планетах, показан в листинге 5.16.

230 Часть II. Основные подходы к разработке приложений Листинг 5.16. Содержимое файла PlanetUpdateCoimandProcessor. Java public class PlanetUpdateCommandProcessor implements XMLCommandProcessor { public Element processXML(Element el) { // О Создать XML-узел результатов Element result=new Element("command");

String id-el.getAttributeValue("id");

result.setAttribute("id", id);

String status=null;

String reason=null;

String planetld=el.getAttributeValue("planetld");

String field=el.getAttributeValue("field");

String value=el.getAttributeValue("value");

// @ Обращение к модели предметной области Planet planet=findPlanet(planetld);

if (planet==null){ status-"failed";

reason="no planet found for id "+planetld;

}else{ Double numValue=new Double(value);

Object[] args=new Object[]{ numValue };

String method = "set"+field.substring(O,1).toUpperCase() +field.substring(1);

Statement statement=new Statement(planet,method,args);

try { // © Обновить модель предметной области statement.execute();

status="ok";

} catch (Exception e) { status="failed";

reason="unable to set value "+value+" for field "+field;

} } result.setAttribute("status",status);

if (reason!=null){ result.setAttribute("reason",reason);

} return result;

} private Planet findPlanet(String planetld) { // О Использовать ORM для модели предметной области return null;

} } Глава 5. Роль сервера в работе Ajax-приложения JDOM используется не только для разбора XML-данных, содержащих ся в запросе, но и для генерации ответа. Корневой узел О и дочерние узлы создаются в методе processXML(). Для доступа к модели предметной обла сти на стороне сервера используется метод f indPlanet () ©;

ему передается идентификатор объекта. Реализация метода f indPlanet () здесь не рассмат ривается;

заметим лишь, что в нем реализуется обмен с базой данных тра диционными способами О. Для обновления модели предметной области © применяется механизм отражения.

Так в общих чертах выглядит архитектура, основанная на использова нии очереди и используемая для объединения частных действий по обновле нию модели предметной области в одну HTTP-транзакцию. Она позволяет не только синхронизировать модели на стороне клиента и на стороне серве ра, но и эффективно управлять трафиком. Как было сказано в разделе 5.3, такое решение может быть использовано совместно с JSF и другими подоб ными средствами, когда структура пользовательского интерфейса и модель взаимодействия существенно зависят от сервера. В данном случае мы лишь реализовали эффективный способ обновления модели предметной области на разных уровнях.

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

5.6. Резюме Мы начали данную главу с рассмотрения основных функций сервера в соста ве Ajax-приложения, а именно с доставки клиентского кода браузеру и предо ставления клиенту данных в процессе работы. Рассматривая эти вопросы, мы учли наиболее популярные языки и инструменты, используемые для создания серверных программ. Они ориентированы на классические Web-приложения, и мы выяснили, насколько они подходят для Ajax. Инструменты, предназна ченные для создания серверных программ, очень быстро изменяются, поэто му, вместо того, чтобы обсуждать конкретные продукты, мы разделили их на категории в зависимости от используемой архитектуры. В результате было выделено три основных направления: архитектура Model2, средства на ос нове компонентов и архитектура, ориентированная на использование служб.

На сегодняшний день есть основания считать, что лучше всего для Ajax под ходит SOA, однако попытки адаптировать другие архитектуры также могут Увенчаться успехом.

232 Часть II. Основные подходы к разработке приложений Далее мы выделили три основных подхода к решению задачи доставки данных от сервера клиенту: ориентированный на содержимое, ориентирован ный на сценарий и ориентированный на данные. Если классическое Web приложение больше всего соответствует архитектуре, ориентированной на содержимое, то приложение Ajax больше укладывается в схему, ориентиро ванную на данные. Взаимодействие, ориентированное на сценарии, занимает промежуточную позицию. Обсуждая архитектуру, ориентированную на дан ные, мы выяснили, что XML — не единственно возможный формат пред ставления данных. В некоторых случаях при доставке информации клиенту может успешно применяться формат JSON.

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

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

5.7. Ресурсы В данной главе были упомянуты некоторые инструменты разработки. Ниже приведены их URL.

Struts (http://struts.apache.org).

Tapestry (http://jakarta.apache.org/tapestry/).

JSF (http://java.sun.com/j2ee/javaserverfaces/faq.html).

PHP-MVC (http://www.phpmvc.net).

По данным исследований (http://wicket.sourceforge.net/Introduc tion.html), в настоящее время только для Java существует более 60 набо ров базовых средств.

JSF — это категория инструментов, в которую входят различные продукты. Кито Манн (Kito Mann), автор книги JavaServer Faces in Action (Manning, 2004), поддерживает Web-портал, посвященный JSF (http://www.jsfcentral.com/). Материалы, подготовленные Грегом Мюр реем (Greg Murray) и его коллегами в ходе обсуждения тем Ajax и JSF, до ступны по адресу https://bpcatalog.dev.java.net/nonav/ajax/jsf-ajax/ frames.html. AjaxFaces представляет собой реализацию JSF для Ajax (http://www.ajaxfaces.com);

этот продукт распространяется на коммер ческой основе. Готовится модификация Apache Open Source MyFaces (http://myfaces.apache.org/sandbox/inputSuggestAjax.html) в соответ ствии с требованиями Ajax.

Глава 5. Роль сервера в работе Ajax-приложения На момент написания данной книги Microsoft Atlas находился в процессе пазработки, но первые реализации скоро должны быть доступны. Менеджер проекта Atlas, Скотт Гатри (Scott Guthrie), публикует материалы, имеющие отношение к Ajax (http://weblogs.asp.net/scottgu/archive/2005/06/28/ 416185.aspx).

Библиотеки JSON-RPC, ориентированные на различные языки програм мирования, находятся по адресу http://www.json-rpc.org/impl.xhtml.

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

Инфор, по, В этой главе...

• Основные характеристики практичного кода • Принципы работы системы оповещения • Набор базовых средств для оповещения • Выделение измененных данных 238 Часть III. Создание профессиональных Ajax-приложений В главе 1 мы обсуждали практичность — одно из основных качеств при кладной программы. Независимо от того, насколько грамотно написан код какие применены технические решения, если приложение недостаточно прак тично, у пользователя останется плохое впечатление от него. Может быть программистам это и покажется несправедливым, но положение вещей имен но таково. Глядя на фотографию Альберта Эйнштейна, обыватель вряд ли поймет, что этот человек внес огромный вклад в формирование научной кар тины мира, но отметит его неряшливый внешний вид и всклокоченные воло сы. Первое впечатление о человеке или предмете очень важно для формиро вания отношения к нему.

В главах 2-5 мы рассмотрели важные технологии и решили ряд интерес ных задач, пользуясь подходом, специфическим для Ajax. Дальнейший нащ разговор пойдет в основном о том, как оформить их. Надо заметить, что рас смотренные примеры внешне выглядят достаточно грубо. Это не удивитель но, ведь мы уделяли основное внимание изяществу самого решения. Теперь нам надо выяснить, что мы можем сделать, чтобы пользователю понравилось созданное нами приложение и он согласился работать с ним по нескольку ча сов в день. Темы, рассмотренные в данной главе, призваны помочь читателю правильно представить Ajax-приложение внешнему миру.

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

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

6.1. Создание качественного приложения Практичность — важнейшая характеристика Ajax-приложений, поскольку их пользователи представляют собой чрезвычайно непостоянную аудиторию.

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

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

6.1.1. Отклик программы Одно из явлений, которые мешают использованию компьютера, — это преры вание нормального хода работы во время выполнения некоторых операций.

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

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

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

Помимо передачи данных по сети, на время отклика влияет также произ водительность клиентского кода. Производительность — чрезвычайно важ ная характеристика приложения, и мы уделим ей внимание в главе 8.

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

240 Часть III. Создание профессиональных Ajax-приложений Какие недостатки станут видны в результате подобного тестирования?

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

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

6.1.3. Согласованность Как уже упоминалось выше, на практичность Ajax-приложений существен ное влияние оказывает тенденция к сближению программ интерфейсов для настольных систем и Web-страниц. Некоторые инструменты, ориентирован ные на Ajax, например Bindows, qooxdoo и Backbase, даже предлагают на боры компонентов, которые выглядят подобно кнопкам, деревьям, таблицам и другим элементам пользовательского интерфейса, типичным для настоль ных систем.

Теперь, чтобы создать качественное приложение, надо одновременно быть разработчиком Web-страниц, специалистом по вопросам практичности и ко нечным пользователем. Наилучший совет в данной ситуации — обеспечить согласованность различных частей приложения. Если в одной части програм мы для отображения окна с дополнительной информацией требуется один щелчок мышью, а в другой части такое же окно открывается двойным щелч ком, пользователь будет испытывать неоправданные затруднения в процессе работы. Если же руководство по выбору пути в пределах Web-узла выполне Глава 6. Информация для пользователя до вами в виде говорящего поросенка, следите за тем, чтобы он внезапно не сменил одежду или не изменил произношение.

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

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

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

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

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

242 Часть III. Создание профессиональных Ajax-приложений Далее в этой главе будет рассмотрен ряд средств, которые можно ре ализовать в рамках Ajax-приложения. Мы уделим много внимания опове щению пользователя о процессах, скрытых от него, например, о вычисле ниях или обмене по сети. Предоставляя пользователю информацию о ходе работы программы, мы повышаем качество взаимодействия с приложени ем. Реализуя оповещение посредством общего базового набора средств, мы обеспечиваем согласованность и упрощаем работу пользователя. Если все сообщения оформлены по одному и тому же принципу, пользователь луч ше воспринимает их.

Рассмотрим различные способы оповещения пользователя о событиях, возникающих во время работы программы.

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

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

6.2.1. Поддержка ответов на собственные запросы Рассмотрим конкретный пример. В главе 5 мы создали приложение для про смотра информации о планетах. Оно позволяло пользователям обновлять некоторые свойства, в частности, задавать диаметр планеты и ее расстояние от Солнца (см. раздел 5.5). Информация, введенная пользователем, передава лась на сервер, а в ответе сервера содержались сведения о том, приняты или отвергнуты изменения. В разделе 5.5.3 было введено понятие очереди команд;

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

Пользователь, работающий с приложением, редактирует свойство и при нимается за другую задачу, может быть, даже переходит к другому окну. На Глава 6. Информация для пользователя пример, обновив сведения о диаметре планеты Меркурий, он больше не уде ляет внимание этому вопросу. В это время значение для обновления оформ ляется в фоновом режиме в виде JavaScript-объекта Command, который поме щается в очередь исходящих сообщений, а затем передается серверу. После этого объект Command перемещается в массив sent и извлекается оттуда при получении ответа. В составе ответа может содержаться информация сразу о нескольких обновлениях. Объект Command отвечает за обработку обновле ния и выполнение необходимых действий.

Давайте вспомним, какой вид имел код приложения перед началом ре структуризации. Ниже показан код метода parseResponse () объекта Com mand. Именно так мы сформировали его в главе 5.

planets.commands.UpdatePropertyCommand.parseResponse=function(docEl){ var attrs=docEl.attributes;

var status=attrs.getNamedItem("status").value;

if (status!="ok"){ var reason=attrs.getNamedItem("message").value;

alert("failed to update "+this.field +" to "+this.value+"\n\n"+reason);

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

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

Failed to update albedo to 180 value out of range. Вне контекста такое сообщение совершенно неинформативно. Мы можем изменить текст, чтобы он выглядело приблизительно так: Failed to update albedo of Mercury..., но при этом действия пользователя все равно прерываются, а именно это го мы и хотели избежать, переходя к асинхронной обработке сообщений.

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

Необходимо более элегантное решение, чем обычное окно с сообщением.

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

244 Часть III. Создание профессиональных Ajax-приложений 12.2. Обработка обновлений, выполненных другими пользователями 'усматриваемое приложение допускает одновременную работу различных юльзователей, поэтому редактирование данных может осуществляться сра у с нескольких клиентских машин. Каждый пользователь хотел бы иметь перативную информацию об изменениях, внесенных другими участниками.

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

Для того чтобы учесть данное требование, мы должны модифицировать [ML-ответ и очередь объектов Command следующим образом. Для каждого за роса, предполагающего обновление модели предметной области на стороне ервера, должна генерироваться метка с информацией о времени (временная [етка). Процесс, выполняющийся на стороне сервера, который учитывает бновления, должен проверять модель, выявлять недавние изменения, вне енные другими пользователями, и включать их в возвращаемый документ.

Ъперь полученный ответ будет выглядеть приблизительно так, как показа о ниже.

•«responses updateTime='1120512761877'> Помимо дескрипторов , содержащих идентификаторы объектов ommand, в составе ответа присутствует также дескриптор , который данном случае определяет расстояние от Венеры до Солнца. Значение этого войства, равное 0,76, задал пользователь Jim. Кроме того, мы можем доба ить к дескриптору верхнего уровня атрибуты, состав и назначение которых ы обсудим несколько позже.

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

[зменения выделены полужирным шрифтом.

ЛИСТИНГ 6.1. Объект CommandQueue // О Глобальная ссылка net.cmdQueues-new Array() ;

// © Дополнительные параметры функции net. CommandQueue=functi on(i d,url,onUpdate, freq){ t hi s. i d=i d;

net.cmdQueues[i d]-thi s;

t hi s. ur l =ur l ;

this.queued=new Array();

this.sent=new Array();

Глава 6. Информация для пользователя this.onUpdate-onOpdate;

// 0 Инициализировать повторные обращения if (freq){ this.repeat(freq);

} this.lastUpdateTime»O;

} net.CommandQueue.prototype.fireRequest=function(){ if (!this.onUpdate && this.queued.length==0){ return;

} // Временная метка var data= "lastUpdate«"+this.lastUpdateTime +"&data=";

for(var i=0;

i

i++){ var cmd=this.queued[i];

if (this.isCommand(cmd)){ data+=cmd.toRequestString();

this.sent[cmd.id]=cmd;

} } this.queued=new ArrayO;

this.loader=new net.ContentLoader( this.url, net.CommandQueue.onload,net.CommandQueue.onerror, "POST",data );

} net.CommandQueue.onload=function(loader) { var xmlDoc=net.req.responseXML;

var elDocRoot=xmlDoc.getElementsByTagName("responses")[0];

var lastUpdate=elDocRoot.attributes.getNamedItem("updateTime") ;

if (parseInt(lastUpdate)>this.lastUpdateTime){ // О Обновленная временная метка this.lastUpdateTime=lastUpdate;

} if (elDocRoot){ for(i=0;

KelDocRoot.childNodes.length;

i++){ elChild=elDocRoot.childNodes[i];

if (elChild.nodeName«-"command"){ var attrs=elChild.attributes;

var id=attrs.getNamedItem("id").value;

var command=net.CommandQueue.sent[id] ;

if (command){ command.parseResponse(elChild);

} }else if (elChild.nodeName=="update"){ if (this.implementsFunc("onUpdate")){ // © Обновленный обработчик this.onUpdate.call(this,elChild);

} } 246 Часть III. Создание профессиональных Ajax-приложений ) } } // 0 Опрос сервера net.CommandQueue.prototype.repeat=function(freq){ this.unrepeat();

if (freq>0){ this.freq-freq;

var cmd="net.cmdQueues["+this.id+"].fireRequest{)" ;

this.repeater=setlnterval(cmd,freq*1000);

} } // О Включение/отключение повторных обращений net.CommandQueue.prototype.unrepeat=function(){ if (this.repeater){ clearInterval(this.repeater);

} this.repeater=null;

—1 • Рассмотрим новые функции, которые мы реализовали, изменив код.

Во-первых, введена глобальная ссылка на очередь объектов Command О.

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

Теперь при вызове конструктора CommandQueue указываются два новых параметра ®. В качестве параметра onUpdate передается объект Function, используемый для поддержки дескрипторов , который теперь мо жет присутствовать в составе ответа. Параметр freq задает числовое зна чение, определяющее интервал в секундах между последовательными обра щениями к серверу за информацией об обновлении. Если значение не равно нулю, конструктор инициализирует обращения к функции repeat () ©, в ко торой для организации повторного выполнения кода использован встроен ный метод JavaScript setlnterval. Метод setlnterval () и метод setTimeout, предоставляющий аналогичные возможности, допускают параметры, задан ные лишь в виде строк, поэтому нельзя непосредственно передавать ссылки на код, подлежащий выполнению. Чтобы обойти эту проблему, мы исполь зуем в теле функции repeat () глобальную переменную и уникальный иден тификатор. Мы также сохраняем ссылку на временной интервал и можем в любой момент прекратить периодический опрос сервера, вызвав функцию unrepeat () в, в теле которой осуществляется обращение к clearlnterval.

Ранее, если очередь команд оказывалась пустой, метод f ireRequest О завершал работу. Теперь проверка выполняется несколько по-другому. При установленном обработчике onUpdate работа функции продолжается, неза висимо от того, есть ли в очереди элементы. Если очередь пуста, то серверу передается пустой запрос, на который сервер передает ответ, содержащий де скрипторы . Вместе с результатами редактирования мы теперь по Глава 6. Информация для пользователя сылаем значение времени, сообщая серверу о моменте внесения изменений ©.

Эту информацию сервер использует для того, чтобы правильно сформиро вать передаваемую информацию об обновлении. Значение времени хранит ся в виде свойства очереди команд и первоначально устанавливается рав ным нулю.

Информацию о времени мы передаем в формате, принятом в системе Unix для хранения даты, т.е. сообщаем число миллисекунд, прошедших с 1 января 1970 года. Такой выбор продиктован соображениями переносимости. Если бы мы выбрали формат более удобный для чтения, нам пришлось бы уделять внимание интернационализации и обеспечению адекватного представления на других платформах. Вопросы интернационализации имеют большое зна чение для Aj ax-приложений, так как для большинства из них планируется доступ по глобальной сети.

В функции onload () мы добавили код, необходимый для обновления со храненной информации о времени О и для разбора дескрипторов 0. Функция обработчика onUpdate вызывается в контексте очереди команд, а элемент DOM, соответствующей дескриптору , передается ей в ка честве параметра.

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

Листинг 6.2. Функция updatePlanets() ДЦ:

function updatePlanets(updateTag){ var attribs=updateTag.attributes;

var planetld=attribs.getNamedltemC'planetld").value;

var planet=solarSystem.planets[planetld];

if (planet){ var fld=attribs.getNamedItem("fieldName").value;

var val=attribs.getNamedItem("value").value;

if (planet.fid){ planet[fId]=val;

}else{ alert('unknown planet attribute *+fld);

} }else{ alert('unknown planet id '+planetld);

} ) я Атрибуты дескриптора предоставляют нам всю информа цию, необходимую для обновления модели предметной области на уровне JavaScript. Конечно, данные, полученные с сервера, могут быть некоррект ными, поэтому нам надо предусмотреть на этот случай соответствующие дей ствия. Например, можно вывести сообщение с описанием проблемы, форми руемое с помощью функции al ert (). Этот вопрос обсуждался в разделе 6.2.1.

Теперь мы реализовали более сложное поведение очереди объектов Com mand, в частности, предусмотрели поддержку обновлений, выполненных дру гими пользователями, включили в состав данных, которыми обмениваются клиент и сервер, информацию о времени и предусмотрели включаемый об 248 Часть III. Создание профессиональных Ajax-приложений работник обновления. Итак, мы несколько продвинулись по пути решения вопроса об асинхронном обновлении модели предметной области и инфор мировании пользователя о внесенных изменениях. В следующем разделе мы рассмотрим вопросы представления информации и влияние ее на нормаль ный ход работы пользователя. Там же мы постараемся отказаться от функ-' ции al ert () в пользу более подходящего решения.

6.3. Создание системы оповещения Функция alert, которую мы применяли до сих пор, позволяет реализо вать лишь примитивное решение, типичное для ранних периодов развития JavaScript, когда Web-страницы были в основном статическими, а фоновая активность — минимальной. Внешний вид окна с сообщением не поддается контролю средствами CSS, поэтому, работая над профессиональным продук том, желательно разработать механизм оповещения, используя технологии, применяемые для остальной части пользовательского интерфейса Ajax. Тем самым обеспечивается дополнительная степень гибкости приложения.

Если вы проанализируете существующие вычислительные системы, то увидите, что в них используются самые различные средства оповещения, раз личающиеся и по степени воздействия на пользователя. Менее всего привле кают внимание такие эффекты, как изменение вида курсора мыши (класси ческий пример песочных часов в системе Windows) или добавление к пик тограмме, представляющей объект, дополнительных элементов, которые поз воляют судить о состоянии папки. Такие простые индикаторы представля ют минимум информации. Более детальные сведения о фоновых событиях можно получить, прочитав сообщение в строке состояния. И наконец, самым информативным является диалоговое окно. На рис. 6.1 показан пример ис пользования соглашений об оповещении, принятых для окружения рабочего стола в системе Unix.

В данном случае папка lost+f ound не доступна текущему пользователю, поэтому к ней добавлено изображение замка. Строка состояния в нижней части окна предоставляет более подробную информацию о содержимом пап ки и в то же время не отвлекает пользователя от основной работы. Окно с сообщением об ошибке отображается тогда, когда пользователь пытается открыть папку, к которой он не имеет доступа. В этом случае действия поль зователя прерываются явно, так как он должен немедленно отреагировать на сообщение.

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

Рис. 6.1. Соглашения, принятые для предоставления информации о состоянии. Внешний вид пиктограммы отражает характеристики объекта (в данном случае права доступа), строка состояния позволяет получить общие сведения, а модальное диалоговое окно — подробную информацию об объекте. В данном случае в качестве примера приведена среда рабочего стола Unix, но аналогичные соглашения действуют во всех популярных графических интерфейсах 6.3.1 Основные принципы оповещения В качестве первого шага нашей работы определим, как должно выглядеть сообщение, предоставляющее информацию пользователю. Логично пред положить, что в него следует включить текстовое описание и, возмож но, пиктограмму.

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

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

Объект Message, код которого приведен в листинге 6.3, создан с учетом этих требований. Этот объект реализует универсальный механизм оповеще ния пользователя. Установив модель оповещения, мы можем оформлять со общения различными способами.

250 Часть 111. Создание профессиональных Ajax-приложений I Листинг 6.3. Объект Message var msg=new Objectt);

| rasg.PRIORITY_LOWb { id:l, lifetime:30, icon:"img/msg_lo.png" J;

msg.PRIORITY_DEFAULT={ id:2, lifetime:60, icon:"img/msg_def.png" };

msg.PRIORITY_HIGH= { id:3, lifetime:-l, icon:"img/msg_hi.png" };

msg.messages=new Array();

I msg.Message=function{id,message,priority,lifetime,icon){ this.id=id;

msg.messages[id]«this;

this.message=message;

this.priority=(priority) ? priority : msg.PRIORITY_DEFAULT.id;

this.lifetime-(lifetime) ? lifetime : this.defaultLifetime();

this.icon»(icon J ? icon : this.defaultlcon();

if (this.lifetime>0){ j this.fader=setTimeout( "msg.messages['"+thia.id+"'].clear()", ', this.lifetime* );

> } msg.Message.prototype.clear=function(){ msg.messages[this.id1«null;

) msg.Message.prototype,defaultLifetime=function(){ if (this.priority<=msg.PRIORITY_LOW.id){ return msg. PRIORITYLOW. lifetime;

}else if {this.priority==msg.PRIORITY_DEFAULT.id){ return msg.PRIORITY_DEFAULT.lifetime;

}else if (this.priority>=msg.PRIORITY_HIGH.id){ return msg.PRIORITY_HIGH.lifetime;

) msg.Message.prototype.defaultlcon=function(){ if (this.priority<=msg.PRIORITY_LOW.id){ return msg.PRIORITY_LOW.icon;

Jelse if (this.priority==msg.PRIORITY_DEFAULT.id){ return msg.PRIORITY_DEFAULT.icon;

Jelse if (this.priority>=msg.PRIORITY_HIGH.id){ return msg.PRIORITY_HIGH.icon;

} _} '_ ш Для системы оповещения мы определили глобальный объект msg, вы полняющий функции пространства имен. В нем содержится ассоциативный массив, позволяющий обращаться к любому сообщению по его уникальному идентификатору. Схема генерации идентификаторов зависит от конкретного приложения.

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

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

Глава 6. Информация для пользователя I Рис. 6.2. Интерфейс системы оповещения в виде строки состояния. Сообщения отображаются в виде пиктограмм Рис. 6.3. Информацию о сообщении в строке состояния можно получить, поместив курсор мыши на пиктограмму. В результате на экране отобразится окно с подсказкой > (Задается для высокоприоритетных сообщений. Оно указывает на то, что со ббщение не должно автоматически удаляться;

решение о прекращении его ;

Отображения принимает либо пользователь, либо функция обратного вызова.

• Ы.2. Определение требований к пользовательскому интерфейсу Применяя термины MVC, мы можем сказать, что нами только что была опи сана модель системы оповещения. Для того чтобы системой можно было пользоваться, надо определить представление. Существует много способов.визуального отображения информации, предназначенной для пользователя.

В данном случае мы выберем вариант строки состояния, в которой инфор мация будет отображаться в виде пиктограмм (рис. 6.2).

Пиктограмма с красным знаком X — стандартное средство оповещения на вязком уровне. Третья слева пиктограмма на рисунке — это шар синего цвета, отбрасывающий тень;

данная пиктограмма переопределяет изображение X.

используемое по умолчанию. Более подробную информацию о сообщении, ^соответствующем каждой пиктограмме в строке состояния, можно получить $ окне подсказки (рис. 6.3).

[ Такой механизм оповещения нельзя назвать навязчивым. Строка состоя •шя занимает относительно мало места на экране, но появление новой пикто траммы заметно для пользователя. Срочные сообщения надо сделать более заметными, поэтому мы отображаем в строке состояния только низкоприо ^ригетные сообщения. Информация с более высоким приоритетом выводится -В диалоговом окне (рис. 6.4), которое отображается на экране до тех пор, дюка пользователь не удалит его.

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

6.4. Реализация базовых средств оповещения I Мы определили два основных элемента пользовательского интерфейса: стрск!

ку состояния и всплывающее диалоговое окно. Теперь приступим к их реаЦ лизации. Система оповещения достаточно сложна, поэтому разобьем работу| над ней на отдельные этапы. Сначала модифицируем объект Message так| чтобы он мог обеспечить свое отображение в разных ситуациях когда <ж представлен в строке состояния в виде пиктограммы, при выводе окна под-*] сказки или в составе всплывающего диалогового окна. Начнем с реализации:

строки состояния.

6.4.1. Отображение пиктограмм в строке состояния Строка состояния должна отображаться на экране и содержать пиктограм-' мы, представляющие активизированные сообщения. Обязанности по воспро изведению конкретных пиктограмм мы делегируем объекту Message. Его можно описать в терминах архитектуры MVC. Средства отображения рас сматриваются как представление, а интерактивные элементы выполняют роль контроллера. Если бы мы предполагали использовать произвольные ме ханизмы отображения, то столкнулись бы с серьезными проблемами при ре ализации системы оповещения. Однако мы не планируем подобное решение.

Более того, для обеспечения согласованности в пределах приложения надо использовать единый механизм оповещения. В листинге 6.4 показан метод, предназначенный для воспроизведения объекта Message.

Листинг 6.4. базовые 'Средства отображения сообц&йрй^ < * * ~""~Щ //О Воспроизведение сообщения rasg.Message.prototype.render=function(el){ if (this.priority<=msg.PRIORITY_LOW,id){ this.renderSmall(el);

Глава 6. Информация для пользователя lelse if (this.priority>=msg.PRIORXTX_DEFAULT.id){ this.renderFull(el);

} ) // @ Вывести в качестве пиктограммы с поддержкой // окна подсказки msg.Message.prototype.renderSmall=function{el){ this.icoTd=document.createElement("div");

var ico=document.createElement("img");

ico.src=this.icon;

ico.classNarae="msg_smalI_icon";

this.icoTd.appendChild(ico);

this.icoTd.messageObj=this;

this. icoTd.onmouseover=msg.moverIconTooltip, this.icoTd.onmouseout=msg.moutlconTooltip;

this.icoTd.onclick=msg.clicklconTooltip;

el.appendChild(this.icoTd);

} // ® Помещение курсора мыши над кнопкой msg.moverIconTooltip=function(e){ var event=e || window.event;

var message=this.messageObj;

var popped=message.popped;

if ('popped){ message. showi?opup(event, f al s e) ;

} } // О Перемещение курсора мши за пределы кнопки msg.moutIconTooltip=function(e){ var message=this.messageObj;

var popped=rnessage.popped;

var pinned=message.pinned;

if {popped && 'pinned){ message.hidePopup();

} ) / / 0 Щелчок мьппью msg. cl i ckIconTool t i p=f unct i on{e){ var event=e || window.event;

var message=this.messageObj;

var popped=message.popped;

var pinned=message.pinned;

var 6xpired=message.expired;

if (popped && pinned){ message.hidePopup{);

if (expired){ message.unrender();

} }else{ message.showPopup(event.true);

} } // © Вывести окно с подсказкой msg.Message.prototype.showPopup=function(event,pinned){ 254 Часть III Создание профессиональных Ajax-приложений this.pinned=pinned;

if (!this.popup){ this.popup=document.createEleraent("dxv");

this.popup.className='popup' ;

this.renderFull(this.popup);

document.body.appendChild(this.popup);

} this.popup.style.display='block ;

var popX=event.clientX;

var popY=event.clientY-xHeight(this.popup)-12;

xMoveTo(this- popup,popX,popY);

if {msg.popper && msg.popper!=this){ msg.popper.hidePopupf);

} this.popped=true;

msg.popper=this;

} // Скрыть окно с подсказкой msg.Message.prototype.hidePopup=function(){ i (this.popped){ if (this.popup){ this.popup.style.display='none' ;

} this.popped=false;

} } • m Мы реализовали для объекта Message высокоуровневый код отображения и определили детали представления в строке состояния. Начнем с высоко уровневого кода. Метод render () О получает в качестве параметра элемент DOM. В зависимости от приоритета сообщения он передает управление либо методу renderSmall() ®, либо методу renderFulK) ©. В строке состояния представлены только сообщения с низким приоритетом, которые отобража ются в виде пиктограмм. При помещении на такую пиктограмму курсора мыши выводится окно с подсказкой (рис. 6.2 и 6.3).

Функция rendersmalio отображает пиктограмму в составе элемента DOM и устанавливает обработчики событий, в процессе работы которых вы водится подсказка.

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

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

Глава 6. Информация для пользователя 6.4.2. Отображение подробных сообщений В диалоговом окне выводятся высокоприоритетные сообщения либо сообще ния, имеющие приоритет по умолчанию. Они отображаются в виде пикто грамм, сопровождаемых строкой текста (рис. 6.4). Такое же представление требуется нам для окон подсказки, соответствующих пиктограммам в строке состояния. Функция showPopupO, отвечающая за вывод подсказки, обраща ется к методу renderFull {), который отображает детальное сообщение. Этот же метод используется для представления информации в диалоговом окне.

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

!

ЛИСТИНГ 6.5. Метод renderFull О / • -ЦЦ msg.Message.prototype.renderFull=functaon(el){ var inTable=(el.tagName=="TBODY");

var topEl=null;

this.row=document.createElement("tr");

if (!inTable){ topEl=document.createElement{"table") ;

var bod=document.createElement("tbody");

topEl.appendChild(bod);

bod.appendChild(this.row);

}else{ topEl=this.row;

} var icoTd=document.createElement("td");

icoTd.valign=icenter' ;

this.row.appendChild(icoTd);

var ico=document.createElement{"img");

ico.src=this.icon;

icoTd.className="msg_large_icon";

icoTd.appendChild(ico);

var txtTd=document.createElement("td");

txtTd.valign-' top' ;

txtTd.className="msg_text";

this.row.appendChild{txtTd);

txtTd.innerHTML=thi s.message;

el.appendChild(topEl);

_} я Метод renderFull () генерирует строку таблицы. Он проверяет элемент DOM и, если тот представляет собой дескриптор

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

256 Часть III Создание профессиональных Ajax-приложений * Заметьте, что мы не используем здесь методы W3C DOM, текст сообще ния включается в состав таблицы посредством свойства innerHTML. Это от крывает дополнительные возможности по представлению HTML-элементов по сравнению с генерацией обычного текстового узла.

6.4.3. Формирование готовой системы На данном этапе нами полностью реализована поддержка пиктограмм в стро ке состояния и отображение текста сообщений. За вывод диалогового окна и строки состояний отвечает высокоуровневый метод render (), код которого показан в листинге 6.6.

Листинг 6.6. Функция msg. render () msg.render=function(msgbar){ if (!msgbar){ msgbar='msgbar' ;

} // О Обеспечить вывод строки состояния msg.msgbarDiv=xGetElementById(msgbar);

if (!msg.msgbarDiv){ msg.msgbarDiv=msg.createBar(msgbar);

} styling.removeAllChildren(msg.msgbarDiv);

var lows=new Array();

var nveds=new Array О ;

var highs=new Array();

// @ Сортировать сообщения в соответствии с приоритетом for (var i in msg.messages){ var message=msg.messages[i];

if (message){ if (message.priority<=msg.PRIORITYLOW.id) { lows.append (message);

}else if (raessage.priority==msg.PRIORITY_DEFAULT.id){ meds.append(message);

Jelse if (message.priority>=msg.PRIORITY_HIGH.id){ highs.append(message);

} } } // © Воспроизвести низкоприоритетные сообщения for (var i=0;

i

i++){ lows fi].render(msg.msgbarDiv);

} // О Воспроизвести сообщения с высоким и средним приоритетом if (meds.length+highs.length>O){ msg.dialog=xGetElementByld(msgbar+"_dialog");

if (!msg.dialog){ msg.dialog=msg.createDialog( msgbar+"_dialog", msg.msgbarDiv, (highs.length>0) );

// 0 Обеспечить отображение диалогового окна } Глава 6. Информация для пользователя styling.removeAHChildren(msg.dialog.tbod);

for (var i=0;

i

i++){ highs[i].render(msg.dialog.tbod);

} for (var i=0;

Kmeds.length;

i++){ meds[i].render(msg.dialog.tbod);

} if (highs.length>0){ msg.dialog.ico.src=msg.PRIORITY_HIGH.icon;

}else{ msg.dialog.ico.src=msg.PRIORITY_DEFAULT.icon;

} ) } // 0 Создать строку состояния msg.createBar=function(id){ var msgbar=document.createElement("div");

msgbar.className='msgbar' ;

msgbar,id=id;

var parentEl=document.body;

parentEl.append{msgbar);

return msgbar;

} // в Создать диалоговое окно msg.createDialog=function(id,bar,isModal){ var dialog=document.createElement("div");

dialog.className='dialog' ;

dialog.id=id;

var tbl=document.createElement("table");

dialog.appendChiId(tbl);

dialog.tbod=document.createElement("tbody");

tbl.appendChild(dialog.tbod);

var closeBatton=document. createElement (" div" );

closeButton.dialog=dialog;

closeButton.onclick=msg.hideDialog;

var closeTxt=document.createTextNode("x");

closeButton.appendChild(closeTxt);

dialog.appendChild(closeButton);

// О Добавить модальный уровень if (isModal){ dialog.modalLayer=document.createElement("div");

dialog.modalLayer.className='modal' ;

dialog.modalLayer.appendChild(dialog);

document.body.appendChild(dialog.modalLayer);

}else{ dialog.clas sName+=' non-modal';

document.body.appendChild(dialog);

} di al og. i co=document. creat eEl ement ("i mg");

di al og. i co. cl assName="msg_di al og_i con";

di al og. i co. di al og=di al og;

di al og. i co. oncl i ck=msg-showDi al og;

bar. appendChi l d(di al og. i co) ;

r et ur n di al og;

} 258 Часть III Создание профессиональных Ajax-приложений // Скрыть диалоговое окно msg. hi deDi al og=f uncti on(e){ var di al ogs ( t hi s. di al og) ? t hi s. di al og : msg. di al og;

i f (di al og){ if (dialog.modalLayer){ dialog.modalLayer.style.display='none' ;

}else{ dialog.style.display='none' ;

} } } // Отобразить диалоговое окно msg.showDialog=function(e){ var dialog=(this.dialog) ? this.dialog : msg,dialog;

if (dialog){ if (dialog.modalLayer){ 1 dialog.modalLayer.style.display* block ;

}else{ dialog.style.display='block';

} } } m К функции render () можно обращаться многократно. В процессе рабо ты она проверяет присутствие компонентов пользовательского интерфейса О, © и по мере необходимости создает их, используя функции createDialog © и createBar ©. Для формирования компонентов интерфейса используют ся стандартные методы обработки элементов DOM. Обработчики событий позволяют отображать и скрывать диалоговое окно.

Для воспроизведения всех сообщений система сначала сортирует их по приоритету и размещает в трех временных массивах @. Затем низкоприори тетные сообщения выводятся в строке состояния ©, а остальные — в диалого вом окне, причем первыми располагаются высокоприоритетные сообщения О.

Для того чтобы реализовать модальное окно, мы помещаем видимые сред ства поддержки диалога в элемент DIV, занимающий весь экран, и блокиру ющий все события, связанные с мышью ©. Для модального элемента DIV определен белый фон с прозрачными пикселями. По этому признаку можно отличить модальное окно. Мы не используем установки прозрачности CSS, потому что в этом случае любой вложенный элемент будет также прозрач ным. Файл CSS для системы оповещения представлен в листинге 6.7.

Листинг 6.7. Содержимое файла msg. ess •msg_small_icon{ height: 32px;

width: 32px;

position:relative;

float:left;

} • ™sg_jiialog_icon { height: 32px;

width: 32px;

Глава 6. Информация для пользователя pos i t i on: r el at i ve;

f l oat : r i ght ;

• msg_large_icon { hei ght : 64px;

wi dth: 64px;

}.msg_text{ f ont-f ami l y: ar i al ;

font-wei ght: l i ght ;

f ont -s i ze: 14pt;

col or : bl ue;

},msgbar{ position:relative;

background-color: white;

border: solid blue lpx;

width: 100%;

height: 38px;

padding: 2px;

}.dialog{ position: absolute;

background-color: white border: solid blue lpx;

width: 420px;

top: 64px;

left: 64px;

padding: 4px;

}.popup{ position;

absolute;

background-color: white;

border: solid blue lpx;

padding: 4px;

}.non-modal{ }.modal{ position: absolute;

top: Opx;

left: Opx;

width: 100%;

height: 100%;

background-image:url(img/modal_overlay.gif);

260 Часть III. Создание профессиональных Ajax-приложений Рис. 6.5. Использование CSS-атрибутов f l o a t позволяет размещать пиктограммы в контейнере любой формы.

В данном примере мы задали квадратную строку состояния;

пиктограммы автоматически перекомпоновались с учетом выравнивания по левому и правому краю Следует заметить, что в GSb-классах msg_small_icon и msg_dialog_icon используется стиль float. Класс msg_small_icon применяется для вос произведения пиктограмм, соответствующих низкоприоритетным сообщени ям. Эти пиктограммы располагаются в ряд, начиная с левого края. Для msgdialog_icon задано выравнивание по правому краю. Базовые средства позволяют отображать строку состояния любой формы и любого размера.

Плавающие элементы располагаются в строке состояния в ряд;

при заполне нии текущего ряда формируется следующий ряд (рис. 6.5).

Теперь, когда мы создали пользовательский интерфейс для объекта Mes sage, надо модифицировать сам объект. Создаваемые сообщения должны иметь возможность включать самих себя в состав пользовательского ин терфейса и удаляться по окончании действия. В листинге 6.8 показаны изменения, которые необходимо внести в состав кода, чтобы обеспечить эти возможности.

Листинг 6.8. Модифицированный объект Message var msg=new Obj ect();

msg.PRIORITY_LOWs= { id: 1, l ifetime: 30, icon: "img/msg_lo.png" } ;

msg.PRIORITY_DEFAULT={ id:2, lifetime:60, icon:"img/msg_def.png" J;

msg.PRIORITY_HIGH= { i d: 3, lifetime:-l, icon: "img/msghi.png" } ;

msg.messages=new ArrayO;

msg.dialog=null;

msg.msgBarDiv=null;

msg.suppressRender=false;

msg.Message=function(id,message,priority,lifetime,icon){ this.id=id;

msg.messages[id]=this;

this.message=message;

this.priority={priority) ? priority : msg. PRIORITYDEFAULT. id;

this.lifetime=(lifetime) ? lifetime : this.defaultLifetime();

this.icon={icon) ? icon : this.defaultlcon();

if (this.lifetime>0){ this.fader=setTimeout( "msg.messages['"+this.id+"'].clear()", this.lifetime* );

} // О Дополнительные параметры if (!msg.suppressRender){ this.attachToBar();

Глава 6. Информация для пользователя } } // @ Дополнительные параметры msg.Message.prototype.attachToBar=function(){ if (!msg.msgbarDiv){ msg.render();

}else if (this.priority==msg.PRIORITY_LOW.id){ this.render(msg.msgbarDiv);

lelsei if (!msg.dialog){ msg.dialog=msg.createDialog( msg.msgbarDiv.id+"_dialog", msg.msgbarDiv, (this.priority==msg.PRIORITY_HIGH.id) );

} this.render(msg.dialog.tbod);

msg.showDialog();

} } msg.Message.prototype.clear=function(){ msg.messages[this.id]=null;

// & Дополнительные параметры if (this.row){ this.row.style.display='none' ;

this. row.messageObj'^nullf this.row^null;

} i f (t hi s. i coTd){ t hi s. i coTd. s t yl e. di s pl ays ' none' ;

t hi s. i coTd. messageObj =nul l ;

t hi s. i coTd=nul l ;

} _ я Мы стремимся упростить работы с системой, поэтому при создании со общения оно автоматически включается в состав пользовательского интер фейса О. Для того чтобы обеспечить воспроизведение нового сообщения, до статочно вызвать конструктор объекта. В зависимости от приоритета сооб щения оно будет помещено либо в строку состояния, либо в диалоговое окно ©. Если нежелательно воспроизводить каждое сообщение, например, когда мы добавляем одновременно несколько сообщений, предусмотрен флаг, поз воляющий отменить автоматическое отображение. В этих случаях после со здания всех необходимых сообщений мы можем вручную вызвать функцию msg.render().

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

Итак, мы получили комплект базовых средств, упрощающих оповещение пользователей. Мы можем включать оповещение вручную или применять со зданную систему в составе других компонентов, предназначенных для по 262 Часть III Создание профессиональных Ajax-приложений вторного использования. В следующем разделе будет продемонстрирована совместная работа системы оповещения с объектом ContentLoader Пользо вателю будет предоставляться информация о загрузке объектов по сети.

6,5. Предоставление информации в запросах В главе 5 мы представили объект ContentLoader, инкапсулирующий сетевой трафик. Применим систему оповещения для информирования пользователя о состоянии запроса. Начнем с формулирования общих требований.

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

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

Для того чтобы реализовать подобное поведение, нам надо создавать объ екты Message на определенных этапах жизненного цикла запроса, а именно:

при его инициализации, а также по завершении или получении информа ции об ошибках. Модифицированный код объекта ContentLoader показан в листинге 6.9.

Листинг б.Э.-Объект ContentLoader с оповещением пользователя net.ContentLoader=function(... ) {... } ;

net.ContentLoader.msgld=l;

net.ContentLoader.prototyped loadXMLDoc:function{url,method,params,contentType){ if {Imethod){ method="GET";

} if {IcontentType && method=="POST"){ contentType='application/x-www-form-urlencoded' ;

I if (window.XMLHttpRequest){ this.req=new XMLHttpRequest();

} else if (window.ActiveXObject){ this.req=new ActiveXObj ect("Microsoft.XMLHTTP");

I if (this.reqH try{ var loader=this;

this.req.onreadystatechange=function(){ loader.onReadyState.call(loader);

) Глава 6 Информация для пользователя t hi s. r eq. open( met hod, ur l, t r ue) ;

i f (contentType)I t hi s. req. set Request Header(' Cont ent -Type', contentType);

} // О Оповещение об инициализации запроса this.notification=new msg.Message( "netOO"+net.ContentLoader.msgld, "loading "+url, msg.PRIORITY_LOW.id, -1, "img/ball-earth.gif" );

net.ContentLoader.msgld++;

this.req.send(params);

}catch (err){ this.onerror.call(this);

} } Ь onReadyState:function(){ var req=this.req;

var ready=req.readyState;

if (ready==net.READY_STATECOMPLETE) { var httpStatus=req.status;

if (httpStatus==200 || httpStatus==*O) { this.onload.call(this);

// @ Удаление существующего оповещения this.notification.clear{);

}else{ this.onerror.call(this);

} } }, // © Сообщение об ошибке defaultError:function(){ var msgTxt="error fetching data!" + "readyState: "+this. req.readyState +"

  • status: "+this.req.status +"
  • headers: "+this.req.getAHResponseHeaders() +"";

    if (this.notification){ this.notification.clear();

    J this.notifxcation=new msg.Message( "neterrOO"+net. ContentLoader.msgld, msgTxt,msg.PRIORITYDEFAULT.id );

    net.ContentLoader.msgld++;

    } >^ • 264 Часть III Создание профессиональных Ajax-приложений Когда мы создаем запрос посредством вызова loadXMLDoct), мы форми руем оповещение О и связываем ссылку на него с объектом ContentLoader.

    Заметьте, что время жизни устанавливается равным -1, поэтому сообщение не будет автоматически удаляться.

    В методе onReadyState() мы удаляем сообщение при благополучном за вершении операции €). При наличии ошибки мы вызываем метод defaultEr ror (), который формирует соответствующее сообщение €>. Чтобы сообщение лучше воспринималось пользователями, вместо обычного текста для его фор мирования применяются средства HTML.

    В листинге 6.10 показан пример страницы, в которой используется моди фицированный объект ContentLoader Листинг 6.10. Страница, предусматривающая оповещение пользователя Notifications test<Vtitle> <link rel=stylesheet type="text/css" href="msg.ess"/> </head> <body> <div class='content'> <p>here is some content. This is what the web Глава 6 Информация для пользователя рис. 6.6. В окне подсказки, которое отображается при помещении курсора на пиктограмму, содержатся сведения об успешной загрузке ресурса Рис 6.7. Если при обработке запроса возникла ошибка, информация о ней отображается в диалоговом окне (в данном случае в окне отображены два сообщения;</p><p> второе информирует об ошибке) application is up to when not being bugged silly by all these messages and notifications and stuff.</p><p> <p>Type in a URL in the box below (from the same domain, see Chapter 7), and hit 'submit'.</p><p> A souped-up contentloader that understands the notification system will be invoked.</p><p> <input id='urlbar' type='text1/></p><p> submit </div> <div id='msgbar' class='msgbar'></div> </body> </html> m Web-страница (рис. 6.6 и 6.7) содержит простую HTML-форму, в которой пользователь может ввести URL. По щелчку на ссылке submit предприни мается попытка загрузить ресурс, на который указывает URL О. В случае успешного завершения операции происходит обращение к функции обратно го вызова — notifyLoaded(). Функция notifyLoaded() не выполняет с ре сурсом никаких действий, а лишь сообщает о его загрузке, создавая объект Message ©.</p><p> Заметьте, что поведение в случае успешной обработки запроса не преду смотрено при реализации системы. Оно обеспечивается посредством обработ чика onload. Благодаря такому решению система может быть адаптирована с учетом различных требований. В примере, приведенном в листинге 6.9, поведение в случае ошибки было реализовано непосредственно в коде. В ре 266 Часть III. Создание профессиональных Ajax-приложений альных приложениях не каждая ошибка, связанная с передачей данных по сети, настолько важна, чтобы отображать сообщение о ней в диалоговом окне.</p><p> Мы предлагаем читателю в качестве упражнения самостоятельно добавить к ContentLoader параметр, определяющий приоритет сообщения об ошибке (либо другим способом изменить обработчик onError для реализации менее строгой политики).</p><p> Итак, мы продемонстрировали, как можно обеспечить взаимодействие разработанной нами системы оповещения с существующим кодом. В следу ющем разделе мы рассмотрим альтернативные соглашения об оповещении и приведем пример их применения.</p><p> 6.6. Информация о новизне данных Система оповещения, созданная нами, предоставляет набор компонентов верхнего уровня, отображающих информацию о системной активности.</p><p> В некоторых случаях пользователю требуются дополнительные сведения, на пример, о том, что некоторые данные были модифицированы. В этом разделе мы модифицируем объект ObjectViewer, который рассматривался в главах 4 и 5, так, чтобы он сообщал пользователю информацию о данных, недавно подвергшихся изменению.</p><p> 6.6.1. Простой способ выделения данных Начнем решение задачи с рассмотрения простого способа выделения данных путем подсветки;</p><p> для этой цели будем использовать инвертирование изобра жения. В пользовательском интерфейсе ObjectViewer используются в основ ном синие и серые тона, поэтому красный цвет будет выделяться на фоне окружающих элементов. Первое, что нам нужно сделать, — это определить дополнительный класс CSS, представляющий недавно измененные данные.</p><p>.new{ background-color: IfOeOdO;</p><p> } Мы выбрали очень простой стиль. Для реального приложения этого недо статочно;</p><p> чтобы интерфейс выглядел хорошо, надо приложить дополнитель ные усилия. В листинге 6.11 показаны изменения кода ObjectViewer, предна значенные для того, чтобы выделить свойства, недавно подвергшиеся редак тированию. В данном случае признак новизны данных автоматически удаля ется по истечении заданного интервала времени.</p><p> Листинг 6.11. ObjectViewer, предусматривающий выделение новых данных'' objviewer,PropertyViewer.prototype.commitEdit=function(value){ if (this.type=objviewer.TYPE_SIMPLE){ this.value=value;</p><p> var valDiv=this.renderSimple();</p><p> var td=this.valTd;</p><p> td.replaceChild(valDiv,td.firstChild);</p> <div class="po5"></div> <div class='stranici1'><b class='temiser'>Pages:</b>     |<a class="kn1" href="/1/20871-1-oglavlenie-noviy-vzglyad-web-prilozhenie-kakim-dolzhen-bit-web-interfeys-znakomstvo-ajax-upravlenie-k.php" title=""> 1 </a>|   ...   |<a class="kn1" href="/1/20871-2-oglavlenie-noviy-vzglyad-web-prilozhenie-kakim-dolzhen-bit-web-interfeys-znakomstvo-ajax-upravlenie-k.php" title=""> 2 </a>|<a class="kn1" href="/1/20871-3-oglavlenie-noviy-vzglyad-web-prilozhenie-kakim-dolzhen-bit-web-interfeys-znakomstvo-ajax-upravlenie-k.php" title=""> 3 </a>|<div class='kr'><a class="kn1" href="/1/20871-4-oglavlenie-noviy-vzglyad-web-prilozhenie-kakim-dolzhen-bit-web-interfeys-znakomstvo-ajax-upravlenie-k.php" title=""> 4 </a></div>|<a class="kn1" href="/1/20871-5-oglavlenie-noviy-vzglyad-web-prilozhenie-kakim-dolzhen-bit-web-interfeys-znakomstvo-ajax-upravlenie-k.php" title=""> 5 </a>|<a class="kn1" href="/1/20871-6-oglavlenie-noviy-vzglyad-web-prilozhenie-kakim-dolzhen-bit-web-interfeys-znakomstvo-ajax-upravlenie-k.php" title=""> 6 </a>|   ...   |<a class="kn1" href="/1/20871-11-oglavlenie-noviy-vzglyad-web-prilozhenie-kakim-dolzhen-bit-web-interfeys-znakomstvo-ajax-upravlenie-k.php" title=""> 11 </a>|</div> <div class="separator2"></div> <div class="po122"> <img class="doc" src="/images/doc.gif" border="0" alt="" ><a class="menusil" href="/1/" title=""><b class="jir">Книги, научные публикации</b></a> </div> <div class="niz2"> <br><br> <noindex> <center> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <!-- dislib-kvadrat (niz) --> <ins class="adsbygoogle" style="display:inline-block;width:336px;height:280px" data-ad-client="ca-pub-9894471784993021" data-ad-slot="3110193131"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </center> </noindex> <br> <div class="naverh"><A href="#verh" >наверх</a><img src="/images/s.gif" hspace="3" width="5" height="11" border="0" alt="" align="top" vspace="3" ></div> </td></tr></table> <table width="100%" cellspacing="0" cellpadding="0" border=0> <tr> <td bgcolor="#CCCCFF" height="1"> </td> </tr> <tr> <td class="menu-niz"> <center> <table cellspacing="0" cellpadding="0" border=0> <tr> <td class="menu-niz1"> |  • <a href="/" title="На главную">Главная</a>  |  • <a href="/admin/contact-kontakti-dissertatsii.php" title="Контакты">Контакты</a> |  </td> </tr> </table> </center> </td> </tr> <tr><td class="line1"></td> </tr> </table></td><td class="line"><img class="lin" src="/images/spaser1.gif" border="0" alt=""></td></tr></table> </td><td valign="top"> <table width="5" border="0" cellspacing="0" cellpadding="0"> <tr> <td></td></tr></table></td></tr></table> </td><td class="site2"></td></tr><tr><td colspan="3"> <noindex> <script type='text/javascript' src='http://recreativ.ru/rcode.97536708c5.js'></script> <script type='text/javascript' src='http://recreativ.ru/rcode.88aa634d1b.js'></script> <script type='text/javascript' src='http://recreativ.ru/rcode.30746632cd.js'></script> </noindex> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td valign="top" class="cap1"> <font color="#808080">© 2011 www.dissers.ru - «Бесплатная электронная библиотека»<br><br> <noindex> Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам. <br> Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, <a href="/admin/contacts.php" title="">напишите нам</a>, мы в течении 1-2 рабочих дней удалим его. </noindex> </font> </div> </td> </tr> </table> <noindex> <!-- Yandex.Metrika counter --> <script src="//mc.yandex.ru/metrika/watch.js" type="text/javascript"></script> <script type="text/javascript"> try { var yaCounter78166 = new Ya.Metrika({id:78166,type:1}); } catch(e) { } </script> <noscript><div><img src="//mc.yandex.ru/watch/78166?cnt-class=1" style="position:absolute; left:-9999px;" alt="" /></div></noscript> <!-- /Yandex.Metrika counter --> </noindex></td></tr></table> </body> </html>