Аннотация
В работе рассматриваются особенности реализации Веб-приложений с насыщенным интерфейсом
(приложений Web 2.0), где активно применяются средства многооконной визуальной компоновки
документов на стороне клиента. Отмечаются трудности при создании функциональных связей
между DHTML-компонентами, расположенными в разных окнах клиентского приложения, вызванные
асинхронным режимом клиент-серверного обмена в среде Интернет. Представлена идея адаптации
схемы взаимодействия компонент из архитектуры MVC Smalltalk и описаны программные механизмы
для простого и удобного внедрения этой схемы в задачах Веб-разработки. Демонстрируется
применение предложенных решений в реальной системе онлайновой биржевой торговли.
Abstract
The paper discusses some aspects of rich user interface implementation in next-generation
Web-applications (Web 2.0 applications), which actively use multi-window and multi-document
design for building representation on the client-side. We describe difficulties involved
into maintaining behavioral relationships between DHTML-components located in different
windows of client application, determined by asynchronous mode of client-server communications
in the Internet. An idea to adapt the MVC Smalltalk-based architectural pattern for
component interaction is evaluated and it is refined into the description of programming
tools for simple and convenient implementation of this pattern in Web projects. In conclusion
we demonstrate viability of the proposed solutions by considering an example of real online
market exchange system.
Содержание
Введение. 3
1. Режимы клиент-серверного взаимодействия и их особенности. 5
2. Обоснование выбора схемы взаимодействия компонент. 14
3. Предлагаемые решения для организации взаимодействия
DHTML-компонент 17
3.1. Модель программной структуры клиентского
приложения. 18
3.2. Представление состояния приложения и
управление изменениями. 21
3.3. Процедуры-триггеры.. 24
4. Пример практического использования механизмов синхронизации. 26
Заключение. 31
Список литературы.. 31
Несмотря на многочисленные
трудности, связанные с реализацией программных систем в среде всемирной паутины
World Wide Web,создание
решений на платформе Веб вот уже более десяти лет образует наиболее
перспективное и динамично развивающееся направление современной индустрии
разработки приложений. Целые классы приложений, которые ранее распространялись
или могли бы распространяться в виде «настольного» (desktop) программного обеспечения, предназначенного для
установки на компьютерах пользователей - такие, как системы интерактивного
общения (конференции, чаты), онлайновой торговли и резервирования (электронные
магазины, аукционы, биржевые площадки, заказ билетов и номеров в отелях),
библиотечные каталоги, интерактивные географические атласы, игры,
автоматизированные рабочие места (администраторов, контент-менеджеров,
корпоративных пользователей) - перемещаются в среду Веб и, в конечном итоге,
этот процесс «вебификации» (weblication) программного
обеспечения проходит очень успешно.
В настоящее время отмечается
рост ожиданий относительно перспектив дальнейшего развития отрасли
Веб-приложений и, даже, предсказывается очередной бум вебификации в набирающей
популярность концепции «Веб нового поколения» (Web 2.0) [1].
Одной из главных, отличительных черт приложений Web 2.0 становится поддержка полноценного,
насыщенного (rich) пользовательского интерфейса, который должен наконец-то приблизиться к
традиционным «настольным» интерфейсам по своим функциональным возможностям,
интерактивности, удобству и эффективности использования.
Приложения подобные Gmail [6],
Google Maps [7], Flickr [8]
оставили в прошлом привычную для разработчиков модель «тонкого» клиента, и
заставили в полной мере работать стек технологий DHTML (JavaScript [5]/DOM [3]/CSS [4]),
заложенный в стандартной архитектуре современного Веб-клиента. Вместе с тем
нельзя не заметить, что создание таких приложений является очень трудоемкой,
сложной в техническом плане и, даже, рискованной задачей, из-за чего
инвестировать в такие решения до недавних пор могли себе позволить только такие
гиганты программной индустрии, как Google или Microsoft.
Однако, положение дел в этой
области быстро меняется. Активное обсуждение и исследование проблем применения
стандартных клиентских Веб-технологий при создании приложений с насыщенным
интерфейсом позволяет выделить такие методы и способы их использования,
овладение которыми не требует знания всех тонкостей DHTML-программирования, и которые – при условии адекватного
инструментального обеспечения – можно сделать легко доступными для
тиражирования. Такой подход, направленный на построение технологической
надстройки над конструкциями DHTML, который сегодня принято обозначать термином AJAX (введенным в [2]), в
настоящее время уже вышел из стадии «лабораторных исследований» и подкрепляется
достаточно солидным списком AJAX-инструментов (см., например, [9] [10]), доступных для применения в реальных задачах
Веб-разработки.
Одним из «соблазнов», от
которого обязательно следует удержаться создателям технологического
инструментария Web 2.0, - это стремление упростить задачу и свести ее к простому (элементарному)
клонированию в среде Веб средств разработки пользовательского интерфейса
«настольных» приложений. К сожалению, опасения в этом плане не беспочвенны.
Так, во многих примерах AJAX-приложений можно увидеть традиционную для
«настольных» интерфейсов жесткую привязку расположения и размеров элементов
интерфейса к координатной системе окна (т.н. пиксельный дизайн), в результате
чего получаются несвойственные Веб визуальные формы, которые не масштабируются
под размер окна. Еще можно обратить внимание на часто встречающиеся примеры
игнорирования «концептуально неудобной» для использования оконной модели
Веб-клиента, заменяемой собственной реализацией диалоговых окон, которые однако
оказывается невозможно «вытащить» за рамки родительского окна.
Тематика настоящей работы
построена вокруг одной из «аксиом», существующей в Веб-программировании -
обеспечение дружелюбности (usability) пользовательского интерфейса приложения, с
учетом того, что все взаимодействия между клиентом и Веб-сервером должны
производиться в асинхронном режиме. Это в одинаковой мере относится как
к выполнению передачи данных, так и к перемещению необходимых для работы
клиентской части приложения программного кода и элементов визуального
интерфейса. К сожалению, эта «аксиома» часто нарушается в существующих AJAX-приложения, когда в
процессе их работы подозрительно часто появляются окна с сообщением:
«Подождите, идет загрузка нужных компонент/данных», а сам интерфейс как бы
зависает. Такая техника фактически воспроизводит поведение «чужеродных» для Веб
Java-апплетов,
которые полностью блокировали страницы, в которые они встраивались, и в
значительной мере поэтому, так и не прижились в Веб-интерфейсах.
В следующем разделе будут
объяснены причины необходимости сохранения асинхронного режима клиент-серверного
обмена в Веб-приложении и, одновременно, отмечены трудности, возникающие при
создании функциональных связей между DHTML-компонентами, расположенными в разных окнах
клиентского приложения. Во втором и третьем разделах представлена идея
адаптации схемы взаимодействия компонент из архитектуры Model-View-Controller и описаны
программные механизмы для простого и удобного применения этой схемы к задачам
Веб-разработки. В последнем разделе демонстрируется применение предложенных
решений в реальной системе онлайновой биржевой торговли.
Когда приложение зависает,
переставая вырабатывать какую-либо видимую реакцию на ввод с клавиатуры или мыши, то это чаще всего
говорит о том, что в ходе работы приложения произошла какая-то фатальная
ошибка, приведшая к зацикливанию программы. Однако такое поведение не всегда
объясняется зацикливанием – одной из распространенных причин этого поведения
может стать недостаточно продуманная реализация системы, когда процессы
(потоки), обслуживающие функционирование форм пользовательского интерфейса,
блокируют свою работу в результате выполнения каких-то продолжительных по
времени операций; либо эти процессы «замораживают» свою работу из-за жесткой
синхронизации с процессами, выполняющими подобные длительные операции.
Пока время, в течение которого
пользовательский интерфейс остается в «замороженном» состоянии, не превышает
определенного физиологически различимого порога, такое поведение системы
выглядит почти незаметным глазу – этому в немалой степени способствует
буферизация событий пользовательского ввода на уровне операционной системы, в
результате чего события не теряются и воспроизводятся после перехода приложения
в «размороженное» состояние. Вместе с тем, начиная с определенных временных
значений подобная блокировка
пользовательского интерфейса дает резкий, ощутимый для человека дискомфорт, а
при более продолжительных задержках с приложением становится просто тяжело
работать.
Очень поучительным в этой связи и, одновременно вызвавшим
большие нарекания со стороны пользователей, моментом стал период выхода в
Интернет клиент-серверных решений, ранее ориентировавшихся на работу в условиях
локальных сетей. Зачастую, вся адаптация
этих систем к Интернет сводилась к реализации соответствующего
прикладного протокола в стеке TCP/IP, но при этом
оставался неизменным традиционно принятый в локальных сетях синхронный режим
доступа к серверу (например, к файловому серверу или серверу баз данных). Этот
период запомнился появлением большого количества клиентских приложений,
«умирающих» после обращения по глобальной сети. Для исправления ситуации
потребовались немалые усилия – пока в дизайне приложений не стали, как
обязательные элементы, появляться кнопки «Stop» и индикаторы прогресса, а разработчики не
научились азам использования асинхронных вызовов и многопоточности для
предотвращения блокировки пользовательского интерфейса во время длительных
операций клиент-серверного обмена .
Вероятно, популярность
приложений, изначально ориентированных на размещение в инфраструктуре Веб,
объясняется как раз тем, что такая ситуация с ними в принципе невозможна,
просто потому что здесь разработчик приложения не имеет никакого прямого
отношения к тому, каким образом (синхронным или асинхронным) выполняется
клиент-серверный обмен в Интернет. В классической модели работы Веб-приложения,
основанной на метафоре навигации по гипертекстовым ссылкам, выполнение
клиент-серверного обмена является функцией программного обеспечения броузера, а
встроенный в страницу программный код начинает работать только после того, как
она полностью загружена (происходит событие onload) и когда внутри нее находится все, чтобы
выполнить всю работу от начала до конца – т.е. до того момента, когда эта
страница заменяется другой страницей.
Таким образом, при том
разделении задач между программными составляющими броузера и Веб-приложения,
которое принято в классической модели, броузер принимает на себя роль
посредника между пользователем и приложением, размещенным в сети, и гарантирует
определенное качество, управляемость работы приложения. При этом все проблемы,
возникающие в связи с обеспечением асинхронного режима доступа к Интернет,
относятся к сфере ответственности разработчиков программного обеспечения
броузера. Например, именно им приходится «ломать голову» над тем, какую часть
страницы из того, что уже получено с сервера, можно отобразить на экране, а
какую – пока рано. Асинхронность также отражена и в самом дизайне Веб-броузера,
где в частности предусмотрена кнопка Stop (чтобы пользователь мог в любой момент остановить загрузку очередной
страницы), кнопки Back и Forward (чтобы можно было в любой момент перейти на предыдущую или следующую из
просмотренных страниц) или, наконец, кнопка [X] в верхнем правом углу окна броузера (чтобы просто штатным образом закрыть
приложение не дожидаясь пока завершится очередное соединение с сервером).
Приложения нового поколения Web 2.0, обладающие
насыщенным пользовательским интерфейсом, основаны на совершенно иных принципах
работы. Например, при реализации интерфейса типа single page interface (описанного в [17]) головная страница, загруженная в окно броузера,
принимает на себя все функции полноценного клиентского приложения. Она может
многократно в ходе своего жизненного цикла запрашивать с сервера нужные ей
данные, порождать дочерние окна (вложенные, диалоговые, всплывающие и т.п.).
При реализации таких приложений становится возможным выбирать режим доступа к
серверу – синхронный или асинхронный. Таким образом разработчик подобных
приложений принимает на себя ответственность за обеспечение новых требований к
пользовательскому интерфейсу, которые раньше в классической схеме
Веб-приложения являлись исключительной прерогативой броузера.
Для иллюстрации этих
особенностей и возможностей воспользуемся следующим несложным примером.
Предположим, что надо реализовать форму (qry_report_from) для задания параметров некоторого отчета, в
котором должны быть представлены данные, выбираемые по интервалу календарных
дат. Такая форма будет иметь два поля для ввода начальной (from_date) и конечной (to_date) даты, а также ряд
дополнительных полей для задания параметров отчета (например, поле «Тип
отчета», позволяющее выбрать краткую или полную форму отчета) и кнопку «Получить
отчет». Кроме того, что дату можно непосредственно ввести в поле с клавиатуры,
это можно сделать и через календарь. Для вызова календаря рядом с полями для
задания дат есть кнопки from_date_button и to_date_button, представленные на следующем рисунке.
В настоящее время в Интернет
можно найти достаточное количество различных реализаций календаря (например, в виде javascript-объектов или
htc-компонент), которыми можно было бы воспользоваться для реализации
рассматриваемого примера и которые обычно поддерживают следующие функции:
-
устанавливать связь с полем, в которое будет помещена
выбранная в календаре дата;
-
позиционировать себя рядом с полем даты
-
при открытии выставляться на дату, указанную в поле
дату либо на текущую дату, если поле пусто;
-
автоматически прятать себя, когда теряется фокус ввода
(поэтому нам не надо будет заботиться о том, чтобы вовремя закрывать календарь
- надо только показать его, например, по клику мыши на соответствующей кнопке).
Конечно самый простой вариант
реализации данного примера – это включить компоненту-календарь непосредственно
в форму, сделав ее невидимой в момент загрузки страницы. Тогда достаточно
определить обработчики событий (onclick) нажатия кнопок from_date_button и to_date_button как:
<script>
var
calendar = null;
</script>
<script for=”window” event=”onload”>
calendar = window.document.
getElementById(“idCalendarComponent”);
</script>
<script for=”from_date_button”
event=”onclick”>
calendar.show(qry_report_form.from_date);
</script>
// аналогично <script for=”to_date_button”
// event=”onclick”>
В данном случае объект calendar доступен нам на
странице с параметрами отчета все время, и он инициализируется в обработчике
события onload. Мы находим эту компоненту (idCalendarComponent) на странице и запоминаем ссылку на нее в
переменной calendar. Соответственно, теперь нет никакой проблемы, чтобы открыть календарь в
контексте нужного поля даты, непосредственно обратившись к его методу calendar.show
Однако, существует много
ситуаций, когда мы не можем себе позволить включить в форму все компоненты,
которые могут потребоваться для ее заполнения. Например, если поле заполняется
с помощью иерархического классификатора большой мощности (например
географического, с континентами, странами, городами), то такой объемный
классификатор разумно получать частями. Кроме того, программный код, отвечающий
за работу календаря может оказаться не таким маленьким, и мы вполне могли бы
пойти на то, чтобы загружать его по требованию, с целью уменьшить время
начальной загрузки страницы.
Если загружать календарь через
отдельное обращение к серверу, то при реализации такого варианта возникает
искушение ничего по большому счету не менять в программном коде, и использовать
синхронный режим обмена между клиентом и сервером, который часто практикуется в
ряде AJAX-приложений.
Раньше это было не так-то просто сделать, потому что все API стандартного клиента
исключали возможности синхронного обращения к серверу и, чтобы этого добиться,
надо было пускаться на разные хитрости. Однако, сегодня стал доступен компонент
XMLHttpRequest, который можно
считать уже стандартным (см. [18], [19]) и в котором заложена возможность синхронного
обращения (атрибут asynс, управляющий режимом обмена). В этом случае представленный выше
программный код может быть переписан следующим образом:
<script>
var
calendar = null;
</script>
<script for=”from_date_button”
event=”onclick”>
if(calendar == null){
calendar = load_calendar();
};
calendar.show(qry_report_form.from_date);
</script>
Недостатком такого решения
очевидно является то, что пока календарь не загрузится (т.е. пока не закончится
выполнение функции load_calendar), вся работа формы замрет, она перестанет отвечать на события клавиатуры и
мыши. В частности пользователь в этот момент не сможет ввести дату в поле
вручную или, вспомнив, что дата находится в буфере обмена (clipboard), вставить ее
оттуда, или, наконец, решить, что ему лучше сначала заполнить другое поле
формы. Другими словами интерфейс сразу сильно теряет в своей дружелюбности,
хотя и остается формально говоря «насыщенным».
Для того, чтобы избавить
реализацию от подобных недостатков, компонента должна загружаться с сервера в
асинхронном режиме. Вообще говоря, данный режим является основным, принятым в
современных стандартных Веб-клиентах, способом обращения к серверу. Причем,
программистам для работы в асинхронном режиме не требуется решать каких-то
запредельно сложных задач, подобных обслуживанию сетевого взаимодействия на
уровне сокетов TCP/IP. В
равной мере, в тексте программ не надо выделять критические секции, поскольку
модель исполнения программного кода, написанного на языке сценариев
(JavaScript, VBScript), является однопотоковой.
Однако сказанное не означает,
что в среде Веб-клиента вообще не приходится сталкиваться с трудностями
реализации в асинхронном режиме. Так, например, следует учитывать, что
отправленный на сервер запрос не исполняется немедленно. В этой связи,
приложение может воспользоваться поступающими с сервера результатами только
спустя какой-то промежуток времени, и в этот период оно должно поддержать
какое-то разумное поведение, отличное от тривиального блокирования
пользовательской активности.
Особенности асинхронного
режима в среде Веб отличают его от традиционных «настольных» клиент-серверных
систем. В последних асинхронно выполняется только передача данных, в то время как все клиентское приложение
(программный код, определения внешних форм и т.п.) находятся на компьютере
пользователя, и их не надо подкачивать с сервера в процессе работы. С другой
стороны, в процессе рабочего цикла Веб-приложения с сервера в асинхронном
режиме передаются не только данные, но и программные компоненты. В результате Веб-приложение
на клиенте приобретает специфическую, динамически изменяющуюся,
полу-детерминированную программную структуру, трансформация которой ведется в
условиях, когда мы точно знаем состояние структуры до и после трансформации,
однако не можем точно предсказать, когда эта
трансформация завершится.
Указанные особенности не
вводят каких-то неразрешимых проблем при программировании клиентской
составляющей Веб-приложения, хотя и делают реализацию значительно более
сложной, требующей от разработчиков определенных навыков и концентрации.
Сегодня на практике применяется простой и, наверное, универсальный подход для
решения задач характерных для асинхронного клиент-серверного обмена,
подразумевающий анализ всех возможных вариантов состояния, в которых могут находиться
отдельные компоненты системы (не загружен, загружен, находится в процессе
загрузки). Исходя из этого, можно предусмотреть отдельную реализацию для
каждого из требующих самостоятельной обработки случаев (или группы случаев).
Продемонстрируем применение
этого подхода на рассмотренном ранее примере с формой для задания параметров
отчета, предполагая, что теперь календарь будет загружаться в отдельном окне,
представленном в виде вложенного фрейма (html-элемент IFRAME). В этом случае необходимо различать состояние
когда календарь загружен, и тогда его можно сразу связать с соответствующим
полем. С другой стороны, если календарь не загружен, то необходимо определить
переменные для представления состояния, в соответствии со значениями которых
календарь после загрузки выполнит то, что ему предписано - откроется рядом с
соответствующим полем даты. Реализация такого поведения может быть поддержана
следующим программным кодом:
<script>
var calendar = null;
var calendar_first_call = true;
var calendar_show_field = null;
</script>
<script for=”from_date_button”
event=”onclick”>
if(calendar_first_call){
load_calendar();
calendar_first_call = false;
};
if(calendar != null){
calendar.show(qry_report_form.from_date);
}
else{
calendar_show_field = qry_report_from.from_date;
};
</script>
<script for=”from_date_button”
event=”onblur”>
calendar_show_field = null;
</script>
Следует обратить внимание, что
на странице с параметрами отчета нам дополнительно пришлось предусмотреть
обработку ситуации, когда календарь находится в процессе загрузки. Для этого
вводится обработчик события onblur, в задачи которого входит отслеживание потери
фокуса в кнопках запуска календаря путем обнуления значения переменной
состояния calendar_show_field.
В результате такого
отслеживания состояния при загрузке календарь сможет отобразить себя в
правильной позиции – рядом с тем полем даты, которое было в фокусе последним, а
если пользователь в ходе загрузки календаря позиционировался на какое-то другое
поле, отличное от даты, то календарь в
момент инициализации не откроется. С учетом этого программный код, определяющий
поведение календаря в момент загрузки в окно IFRAME, может принимать следующий вид:
<script for=”calendar” event=”onload”>
parent.calendar = this;
if(parent.calendar_show_field != null){
this.show(parent.calendar_show_field);
}
else{
this.hide();
};
</script>
Представленный пример наглядно
иллюстрирует недостатки распространенного сегодня подхода к реализации, который
предполагает анализ набора всех возможных комбинаций состояния компонент
системы и создание отдельного варианта реализации для каждого из случаев, требующих
самостоятельной обработки. Видно, что это приводит к дублированию программного
кода, когда одно и то же действие (в примере – открытие календаря) вынужденно
реализуется с использованием разных механизмов и в разных местах (на странице с
параметрами отчета и в коде календаря, загружаемого в IFRAME). Если нужная компонента (в данном случае –
календарь) загружена, то для передачи управления в нее достаточно простых
прямых вызовов. С другой стороны, когда компонента не загружена или находится в
процессе загрузки, то приходится направлять взаимодействие в обратную сторону.
Для этого оказывается необходимым вводить для представления состояния
переменные, в соответствии с которым компонента на фазе своей инициализации
выполнит нужные действия (т.е. действия, которые при прямом обращении
производились непосредственными вызовами). Подобная техника является гораздо
более трудоемкой по сравнению с синхронной подкачкой, но это на данный момент
представляется неизбежной платой за обеспечение требований к построению удобного,
насыщенного пользовательского Веб-интерфейса, по сравнению с которыми проблемы
реализации уходят на второй план.
Отмеченные на этом простом
примере сложности многократно увеличиваются, когда мы переходим к рассмотрению
реальных Веб-систем. К числу таких, например, можно отнести биржевой торговый
терминал (см. раздел 4), в котором требуется синхронизировать работу многих
компонент - окна с котировками и графиками изменения цены финансовых
инструментов; управление номенклатурой торгуемых инструментов, отображаемой на
экране, и т.п. Качественное программирование подобных систем является
действительно очень сложной задачей, когда приходится тщательно проектировать и
реализовывать не только логику работы самого приложения, но и учитывать внешние
факторы влияющее на его работоспособность, например, скорость соединения с
сервером по каналам связи.
Решение проблемы
взаимодействия между компонентами на стороне Веб-клиента в промышленных
приложениях видится не в таком упрощении разработки, которое легко достигается
за счет использования синхронной подкачки программного кода с сервера (что
неизбежно сказывается на реактивности самого приложения), а за счет создания
оригинальных механизмов, которые могли бы обеспечить реализацию взаимодействия
компонент без ущерба для быстродействия приложения в асинхронном режиме. Один из таких
механизмов, основанный на использовании процедур-триггеров для определения
взаимодействия между программными компонентами, будет предложен в настоящей
работе.
Проблемы, существующие при
реализации насыщенных интерфейсов Веб-приложений, легко объяснимы. Приступая к
решению задачи синхронизации поведения компонент в системе пользовательского
интерфейса, с одной стороны, мы имеем довольно необычную,
полу-детерминированную динамическую систему (где компонента может находиться в
различных состояниях готовности к выполнению действий) . С другой стороны, мы
пытаемся воспользоваться тем инструментарием для выполнения задач синхронизации
поведения, который традиционно применяется в современных системах
пользовательского интерфейса, где состояние всех программных компонент
стабильно.
Хотя особенности, отмечаемые
при реализации насыщенных Веб-интерфейсов не типичны для современных средств
поддержки разработки, в недавнем прошлом можно найти программные средства с
аналогичными свойствами. Прежде всего имеется ввиду широко известная
архитектура пользовательского интерфейса, названная Model-View-Controller (MVC) в той своей первоначальной инкарнации, когда она
появилась в языке Smalltalk-80 [11] [12].
Система интерактивного
пользовательского интерфейса, реализуемая на основе этой архитектуры, содержит
три четко выделенных уровня, так называемая MVC-триада: модельный слой (model), инкапсулирующий представление данных и
реализацию бизнес-логики приложения; слой презентации (view), определяющий правила формирования внешних форм,
отображающих состояние приложения на экране; а также управляющий слой (controller), отвечающий за
преобразование событий пользовательского интерфейса (нажатие кнопок клавиатуры,
мыши и т.п.) в вызовы операций бизнес логики. Взаимодействие между этими слоями
подчиняется жесткой системе ограничений, в соответствии с которой модельный
слой не должен иметь прямых обращений к презентационному и управляющему слоям;
слой презентации получает уведомления об изменении состояния приложения через
механизм наблюдателя (observer); наконец, управляющий слой не может
непосредственно модифицировать внешнюю форму и может воздействовать на
состояние слоя презентации только опосредовано, через модификацию состояния
модельного слоя.
Сегодня, в приложении к
современным системам поддержки разработки пользовательского интерфейса,
использование MVC-архитектуры в основном сводится к выделению тех технологических
преимуществ, которые вытекают из четкого концептуального отделения данных и
логики приложения от определения форм внешнего интерфейса. В частности, этот
принцип разделения слоев, принятый в MVC, можно увидеть в основе многочисленных
сервер-ориентированных систем для создания Веб-приложений (Struts, Spring Web MVC., Java Server Faces и
др.).
Однако следует отметить, что
применение MVC для построения пользовательского интерфейса в среде Smalltalk преследовало не
только концептуальные, но и определенные операционные цели, связанные с
необходимостью поддержки динамически конфигурируемой архитектуры (pluggable architecture), что в свою
очередь позволило обеспечить возможность оперативного подключения и отключения
компонент внешнего интерфейса в работающем приложении (без его остановки и
перезапуска). Этот операционный подход MVC в Smalltalk оказывается очень близок к тем условиям, в
которых функционируют насыщенные клиентские интерфейсы в среде Веб, и поэтому
будет целесообразным более подробно остановиться на введенных в Smalltalk механизмах
поддержки взаимодействия программных слоев MVC.
В общих словах, свойства
динамически конфигурируемой архитектуры пользовательского интерфейса
обеспечиваются за счет накладываемого в триаде MVC требования отсутствия прямых
зависимостей модельного слоя по отношению к слоям управления и презентации. Это
требование, в свою очередь, приводит к необходимости реализации потока
синхронизации изменений между модельным и презентационным слоями, обращенном в
обратном направлении по отношению к основному потоку управления (так называемая
pull-синхронизация). Последнее предполагает, что разработчику должны быть
доступны программные механизмы, необходимые для реализации pull-синхронизации –
известные также как средства квази-непрерывного, статусного взаимодействия
(если пользоваться терминологией из области статусно-событийного анализа,
представленной в работах [13] [14]).
В роли базового программного
средства для pull-взаимодействия между компонентами MVC в Smalltalk было предложено использовать механизм Наблюдателя
(Observer-Observable, Publish-Subscribe), который
однако, как показал опыт практического использования, оказался инструментом,
доступным только для системных программистов высокого уровня квалификации (собственно, на этот уровень
рассчитывалась тогда MVC). В частности, одной из наиболее неприятных проблем, характерных для
подобных процедурных механизмов pull-синхронизации, является отсутствие
встроенного механизма, позволяющего предотвратить зацикливание программы.
Отмеченные возможности
архитектуры MVC, заложенные в Smalltalk, нашли свое развитие в еще одной известной
реализации – Java Swing [15], где все стандартные элементы форм в
пользовательском интерфейсе (кнопки, поля ввода, меню, таблицы и т.п.)
построены по принципам MVC с использованием механизма Наблюдателя [16]. За счет
этого в Swing обеспечивается уникальная возможность настройки внешнего вида стандартных
компонент пользовательского интерфейса, который может в процессе работы
приложения динамически подстраиваться под образ элементов из различных
операционных систем (pluggable look-and-feel) путем простой замены объектов в слое, отвечающем
за визуализацию элементов. С другой стороны показательно, что в Swing для компоновки
прикладных форм – т.е. решения задач, относящихся к компетенции прикладного, а
не системного программиста - схема MVC и механизм Наблюдателя не используются. Здесь
применяются обычные для современных систем разработки пользовательского
интерфейса подходы: обработка событий, прямое манипулирование атрибутами
объектов и пр..
Идея адаптировать парадигму
взаимодействия компонент в MVC к задачам построения клиентской архитектуры
Веб-приложений с насыщенным пользовательским интерфейсом кажется очень
перспективной. Особенно интересными и важными с практической точки зрения
представляются такие фундаментальные свойства синхронизации по схеме MVC, как опосредованность
(mediated interaction) и статусность (statefulness).
Опосредованность означает, что
в ходе своего взаимодействия ни одна из компонент не должна получать или
хранить прямые ссылки на объекты (атрибуты, методы и т.п.) других
компонент. Применительно к задачам
реализации многооконных форм пользовательского интерфейса, это свойство
позволяет избежать потенциальных и очень распространенных ошибок, возникающих
при установке прямых ссылок между документами, представленными в разных окнах.
Хотя для определенной топологии окон действительно можно не задумываться о
корректности ссылок, в частности, если они установлены на документы,
размещенные в родительских окнах (т.к. дочернее окно и документ в нем не могут
существовать без своего родителя). Однако, в случае, когда страница
представлена в виде фрейма, а окна фрейма всегда расположены на одном уровне
иерархии, то уже нельзя точно сказать, в каком из окон загрузка документа
произойдет первой в условиях асинхронного режима. Из-за этого гарантировать
правильность значений прямых ссылок в таких документах невозможно.
Статусность означает, что
механизм взаимодействия компонент должен подчиняться более сложным правилам диспетчеризации
событий, чем простая передача сообщений между модулями, поскольку
компонента-адресат может находиться в состоянии загрузки, а следовательно
существует реальный риск потери сообщения. Во избежании этого, механизм
диспетчеризации должен обладать состояниями, в которых аккумулируется история
взаимодействия компонент, чтобы всегда в момент загрузки адресат смог
прореагировать на отправленное ему отложенное сообщение.
В предыдущих разделах были
описаны особенности взаимодействия DHTML-компонент в Веб-приложении с насыщенным
пользовательским интерфейсом, где применяется режим асинхронной «сборки»
документов в программной среде клиента. Представлена идея использования
опробованного в архитектуре MVC-Smalltalk метода организации связей между компонентами,
который позволяет избежать трудностей, возникающих при обычном, «комбинаторном»
варианте реализации такого взаимодействия.
В настоящем разделе
описываются программные средства, которые позволяют разработчику Веб-приложений
легко и удобно использовать MVC-подобные методы опосредованного статусного
взаимодействия DHTML-компонент для синхронизации их совместного поведения.
Получаемая при этом архитектура является достаточно универсальной и включает в
себя три базовых механизма:
-
механизм представления структуры приложения, который
позволяет реализовать более эффективную и управляемую модель по сравнению с
той, которая поддерживается в DHTML;
-
механизм представления состояния приложения (переменных
приложения), который помимо функций общей памяти, разделяемой в контекстах всех
документов (окон) приложения, обладает еще функцией регистрации изменений
(change management);
-
механизм процедур-триггеров, предлагаемый в качестве
облегченного средства для реализации функций Наблюдателя.
В ходе последующего изложения
мы сосредоточимся в основном на технической стороне данной архитектуры,
откладывая демонстрацию ее практического применения до последнего раздела, где
будет представлен пример внедрения предложенных решений в реальной системе
онлайновой биржевой торговли.
Одним из базовых инструментов DHTML-программирования,
который разработчики активно используют при создании насыщенных интерфейсов
Веб-приложений, являются средства визуальной компоновки документов на стороне
клиента. Например, с помощью вложенных фреймовых окон (задаваемых посредством html-тегов frameset, frame
и iframe)
можно создавать составные документы, которые способны гораздо более рационально
распоряжаться пространством внутри отдельного окна броузера по сравнению с
обычными, простыми документами. В равной мере, осваивая возможности открытия
дополнительных окон броузера (например, через обращение к функции window.open), можно эффективно
использовать третье (оконное) измерение пользовательского интерфейса. В
частности, именно эта техника часто используется в «настольных» системах для
реализации сложных сценариев диалога с пользователем.
Применение таких средств
заставляет нас радикально пересматривать представление о тех формах, которые
может принимать выполняемое на стороне клиента приложение. Раньше, в
классической модели Веб, можно было говорить о том, что приложение погружено в
документ, а сама парадигма взаимодействия пользователя с серверной частью
приложения сценарно состояла в прохождении через последовательность
мини-приложений, которые в определенном порядке загружаются и выполняются на
стороне клиента. Теперь, в интерфейсе Web 2.0, приложение может образовывать динамическую
многомодульную структуру, в рамках которой операции добавления, удаления и
замены модулей-документов в пользовательском интерфейсе составляют базовый
функционал рабочего цикла приложения.
Источником больших проблем для
разработки является неэффективное представление
структуры приложения, которое поддерживается в модели DHTML. Во первых, эта
структура описывает исключительно визуальный аспект компоновки документов
приложения и, поэтому, является довольно сложной и «нерегулярной». Если
навигация в направлении снизу-вверх по ссылкам parent,top,opener не вызывает проблем, то перемещение в обратном
направлении требует обхода дерева элементов родительского документа и разбора
многочисленных вариантов компоновки составных документов. Задача осложняется еще
тем, что перейти из главного, стартового окна приложения в дочерние окна нельзя
штатно, поскольку в броузере не поддерживается список окон, открытых из
контекста текущего окна. Следовательно программисту необходимо заводить
дополнительные переменные, чтобы представить номенклатуру открытых окон. Кроме
того, что структура в модели DHTML представляет сложности для навигации, она еще
является плохо управляемой, поскольку нельзя четко отследить подключение и
отключение документов к структуре приложения.
Одним из первых шагов, который
следует сделать в направлении создания удобной архитектуры для разработки
Веб-интерфейсов, является материализация программной структуры приложения в
виде глобального объекта. Эта структура должна вестись параллельно представлению
визуальной компоновки документов, которое поддерживается в модели DHTML. Она может,
например, представлять собой простой массив ссылок на все документы приложения,
находящиеся в текущий момент на стороне клиента. В функции глобального объекта,
отвечающего за ведение этой структуры, должна входить возможность связывания
дополнительной обработки при выполнении операций присоединения нового документа
к приложению, изменения состояния готовности документа к работе (произошло
событие onload), а также исключение документа из структуры приложения.
Поддержка описанной выше
функциональности не представляется чем-то сложным. Для этого необходимо
выделить главный (стартовый) документ, включив в него программный код,
отвечающий за инициализацию и ведение структуры приложения. Кроме того, в
каждый из других документов приложения следует включить функции, отвечающие за
регистрацию в этой структуре состояния документа. Фрагмент программного кода,
который выполняется при запуске приложения, может выглядеть следующим образом:
<!-- инициализация
менеджера структуры приложения -->
<script
src='application-manager.js'></script>
// application-manager.js
var application = {
windows: [window],
registerWindow: function(oWin){ ... },
registerWindowOnload: function(oWin){ ... },
unregisterWindow: function(oWin){ ... }
};
function OnApplicationUnload(){
for(var i=1; i< application.windows.length; i++){
var
oWin = application.windows[i];
if(oWin != null && oWin.top == oWin)
oWin.close();
};
}
window.attachEvent(“onunload”,OnApplicationUnload);
Видно, что при запуске
менеджера приложения создается специальный объект, который встраивается в
контекст окна головного документа как значение атрибута window.application. Далее, в этом
объекте создается массив для представления окон, в которых будут размещаться
документы приложения. В момент старта в массиве сохраняется ссылка на само
главное окно. Кроме того, объявляются методы, которые позволяют регистрировать
изменение состояния прочих окон приложения – загрузку в них документов;
наступление события, свидетельствующего об окончании загрузки, а также о
завершение существования документа. Следует обратить внимание на добавленный в
модуль application-manager.js обработчик события OnApplicationUnload, в
задачи которого входит автоматическое закрытие всех дополнительных окон в
момент завершения работы главного окна приложения.
Документы подключаются к
менеджеру приложения и регистрируют себя в его структуре следующим образом:
<!—- подключение к менеджеру приложения -->
<script src='application-worker.js'></script>
// application-worker.js
var application = null;
if(window.top != window){
application = window.top.application;
} else
{
try{
application = window.opener.application;
}
catch(e){};
if(application == null)
window.close();
};
application.registerWindow(window);
function infoApplicationOnLoad()
{
application.registerWindowOnload(window);
}
function infoApplicationUnload()
{
application.unregisterWindow(window);
}
window.attachEvent(“onload”,infoApplicationOnload);
window.attachEvent(“onunload”,infoApplicationUnload);
В первой части скрипта
решается задача подключения к менеджеру приложения. Если нам удается найти
головную страницу, то в текущем окне сохраняется ссылка на объект application (таким образом
этот объект оказывается разделенным между всеми окнами приложения). В случае,
когда объект application не найден, а это означает, что окно каким-то образом вызвано вне контекста
приложения, мы просто закрываем его программным образом (возможно при этом следует
показать какое-то уведомление). Заключительная часть программы служит для
уведомления менеджера приложения об изменениях в состоянии документа,
загруженного в данное окно.
На первый взгляд может
показаться, что единственным полезным результатом от использования предложенной
модели является то, что в момент закрытия главного окна будут автоматически
закрыты все дополнительные окна, созданные в ходе работы приложения. Однако
главное достоинство этой модели заключается в том, что на ее основе можно в дальнейшем
строить стек программных сервисов (сервисов уровня приложения), которые могут
без изменений повторно использоваться в самых разных по схеме своей визуальной
компоновки приложениях.
Следующим шагом в поддержке
архитектуры клиентской составляющей Веб-приложения является создание механизма
для представления переменных состояния приложения, для чего уже есть
необходимая основа – объект application, разделяемый всеми документами приложения. В
рамках этого механизма должны быть представлены не только функции общей памяти,
позволяющие ассоциировать названия переменных и их текущие значения, но и
функции регистрации изменений (change management), позволяющие отслеживать транзакции при смене
состояния и выделять, какие именно переменные модифицируются внутри транзакции.
В настоящем разделе мы не
будем прибегать к детальному описанию выполненной нами реализации механизма
поддержки глобальных переменных приложения, а остановимся только на тех существенных
технических деталях, которые оказалось необходимо учитывать в ходе разработки
(это представляется полезным для программистов, которые могут захотеть создать
подобный механизм самостоятельно):
-
были отмечены проблемы с означиванием переменных
приложения объектами JavaScript, когда такой объект создается в контексте окна
документа, закрываемого в ходе работы приложения. Результат зависит от
топологического отношения этого окна к главному окну. Если закрытое окно было
вложено в главное, то объект получит ограниченную функциональность (будут
доступны только атрибуты и недоступны методы; это относится и к встроенным
объектам – Array, Date и др.). Если закрытое окно является дополнительным окном
броузера, то сам объект может оказаться «разрушенным» (такой эффект
проявляется, по крайней мере, в броузере Internet Explorer). Чтобы не
сталкиваться с подобными ситуациями при присваивании объектов JavaScript
переменным приложения их следует «клонировать» в контексте главного окна
приложения.
-
аналогичные проблемы возникают при означивании
переменных ссылками на объекты DOM. Эти ссылки следует обнулять в момент, когда
контекст, в котором они были созданы, завершает свою работу (такую
функциональность можно встроить в реализацию метода unregisterWindow объекта
application).
-
операция присваивания переменных состояния должна
позволять изменить значения нескольких переменных одновременно. Для этого можно
использовать нотацию JSON, передавая в качестве аргумента операции выражение
вида {name:value,...}.
-
при выполнении присваивания переменной значения следует
отслеживать ситуацию, когда это значение не изменяется. Для объектов такое
сравнение должно быть «глубоким». Если значение не изменилось, то присваивание
не должно отражаться в транзакции – в результате механизм приобретает функции
«охранника», предотвращающего очевидные варианты зацикливания программы и,
одновременно, оптимизирующего работу системы.
Интерфейс для доступа к
переменным приложения должен включать в себя функции, позволяющие присваивать
значения переменным и получать их текущие значения. Эти операции должны быть
доступны в контексте любого из окон приложения (для этого достаточно включить
их в application-worker.js):
value = appvarGet(name) //
получить значение
appvarSet(name,value) //
присвоить значение
appvarSet({name:value,...}) // присвоить значения
// нескольким
переменным
Для иллюстрации вернемся к
примеру, где была описана форма запроса для построения отчета с полями дат,
заполняемых с помощью календаря. Поведение DHTML-компоненты – календаря в этом
примере полностью определялось значением локальной переменной calendar_show_field, которая либо
указывала на поле даты, в контексте которого календарь должен открываться, либо
была пуста (тогда календарь переводится в «спрятанное» состояние). Поскольку
эта переменная играет роль «синхронизатора» в поведении компонент,
расположенных в разных документах приложения, ее следует представить в виде
глобальной переменной, как показано в следующем фрагменте кода:
<script>
var calendar_first_call = true;
</script>
<script for=”from_date_button”
event=”onclick”>
if(calendar_first_call){
load_calendar();
calendar_first_call = false;
};
</script>
<script for=”body” event=”onfocusin”>
var
oSrc = window.event.srcElement;
if(oSrc.tagName == ‘INPUT’ &&
oSrc.name == “from_date_button”)
appvarSet(“$calendar_show_field”,
qry_report_form.from_date);
else
if(oSrc.tagName == ‘INPUT’ &&
oSrc.name == “to_date_button”)
appvarSet(“$calendar_show_field”,
qry_report_form.to_date);
else
if(!(oSrc.tagName == ‘IFRAME’ &&
oSrc.id == “idCalendarIframe”))
appvarSet(“$calendar_show_field”,null);
</script>
Заметим, что в принятых нами
соглашениях имена глобальных переменных начинаются с символа $, чтобы
синтаксически отличать эти идентификаторы от прочих переменных в программном
коде. Причины такой идентификации станут понятны из следующего раздела.
Взаимодействие компонент по
схеме MVC
представляет собой неэлементарный двухфазный процесс, ход которого на первой
фазе нацелен на поддержание в актуальном состоянии значений переменных
приложения. Эта задача, как показано выше, решается с помощью функций обработки
событий, где модификация состояния приложения производится путем простого
означивания соответствующих глобальных переменных.
На второй фазе MVC-взаимодействия в ответ
на изменения текущих значений глобальных переменных должна производиться
трансформация локального состояния DHTML-компонент и актуализация внешнего представления
документов на экране. Для обеспечения такой формы поведения компонент должны
быть реализованы функции наблюдения (по аналогии с функциями обработки
событий), выполняющие следующие действия:
-
инициализация подписки, в обязанности которой входит
найти наблюдаемый объект и подписаться на получение уведомлений об изменениях
состояния этого объекта (при этом подписчик передает в качестве параметра
перечень названий переменных, на изменения которых он будет реагировать);
-
инициализация состояния наблюдающих DHTML-компонент,
когда на начальном этапе с использованием текущих значений наблюдаемых
глобальных переменных производится актуализация локального состояния и, как
следствие, внешнего представления документов;
-
рабочий цикл, в ходе которого наблюдаемый объект
рассылает уведомления об изменениях своего состояния и производится
синхронизация состояния наблюдающих компонент, подписавшихся на получение
уведомления.
-
завершение подписки, когда «время жизни» компоненты
заканчивается.
Конечно, такой функционал
наблюдателя может быть реализован с помощью стандартных программных средств.
Однако, представляемый в этом разделе механизм процедур-триггеров предлагает
более облегченный способ реализации функций наблюдения и синхронизации
состояния компонент визуального интерфейса, по сравнению со стандартными
средствами. В конечном итоге мы рассчитываем хотя бы частично компенсировать
очевидные с точки зрения Веб-программистов издержки от внедрения схемы MVC-взаимодействия - это
примерно двукратное увеличение числа элементов программной обработки (учитывая
необходимость реализации обработчиков событий и обработчиков-наблюдателей).
Процедура-триггер представляет
собой простой фрагмент программного кода на языке JavaScript, который может быть определен в теле документа
при помощи тега script (<script type=”text/js-trigger”>...</script>) – аналогично нотации, используемой для
декларирования функций обработки событий
(<script for=”...”
event=”...”>...</script>). Программный
код, содержащийся в теле триггера, должен реализовывать содержательную часть
задачи синхронизации – т.е. изменить локальное состояние DHTML-компоненты (или
группы компонент), приведя ее в соответствие с текущими значениями глобальных
переменных приложения. В теле этой процедуры можно объединить действия, которые
производятся на шаге инициализации компоненты, с теми действиями, которые
выполняются в рабочем цикле. Это не представляет затруднений, поскольку
процедура-триггер может опрашивать как текущие значения переменных приложения,
так и текущее состояние самой компоненты.
На практике код инициализации
чаще всего не отличается от кода рабочего цикла. Например, в случае с
календарем, триггер в документе-календаре может иметь вид:
<script type=”text/js-trigger”>
if($calendar_show_field != null)
calendar.show($calendar_show_field);
else
calendar.hide();
</script>
При использовании механизма
триггеров в задачи программиста входит только объявление триггеров и написание
нужного программного кода для синхронизации. Все прочие задачи, необходимые для
реализации функций наблюдения, берет на себя разработанный нами монитор для
поддержки процедур-триггеров:
-
инициализация подписки, для чего из тела триггера
извлекаются все вхождения переменных приложения, которые начинаются с символа
доллар; полученный список переменных
используется при регистрации подписки;
-
инициализация компоненты производится путем выполнения
тела триггера, при этом гарантируется, что документ, в котором объявлен
триггер, будет находиться в состоянии «загружен» (т.е. по наступлению события
onload);
-
обслуживание рабочего цикла, которое также реализуется
с помощью повторного выполнения тела триггера;
-
завершение подписки может быть отслежено по моменту
завершения работы документа, в котором определен триггер.
При выполнении тела триггера
производится означивание всех вхождений переменных приложения их текущими
значениями (или ссылками на значения). Интересно отметить, что триггеры будут
корректно работать как с прямыми уведомлениями об изменениях (когда значения
переменных состояния можно получать непосредственным опросом глобальных
переменных), так и с отложенными уведомлениями (например, через события,
которые помещаются в стандартную очередь событий операционной системы) – когда
значения переменных необходимо извлекать из параметров события.
Процедура-триггер может быть
также объявлена динамически, с помощью вызова функций создания и удаления
триггеров:
createTrigger(triggerId,triggerBody)
// создать триггер
deleteTrigger(triggerId) // удалить триггер
Реализация монитора для
обслуживания триггеров не составляет большого труда. Выше мы фактически описали
правила трансляции триггеров в процедуры на JavaScript’е, реализующие функции наблюдения. Отметим, что
триггеры не сужают доступные программисту возможности при реализации алгоритмов
синхронизации. Здесь можно использовать все операции DHTML, в том числе выполнять динамическое добавление и
удаление элементов во внешней форме, равно как и порождать новые триггеры
(createTrigger/deleteTrigger). Единственное, что запрещено делать в триггерах – это
пытаться означивать глобальные переменные приложения, выстраивая цепочки
вызовов, которые могут привести к зацикливанию. Последнее ограничение полностью
соответствует схеме взаимодействия слоев в парадигме MVC, где слой презентации не может менять состояние
модели.
В настоящем разделе будет
рассмотрен пример использования описанных нами программных механизмов при
решении задач синхронизации поведения DHTML-компонент в реальном проекте. Система разработана
в 2005 году по заказу НП «АТС» для проведения учебных биржевых торгов
фьючерсными контрактами на поставку электроэнергии и использовалась в
методических целях для апробации правил торговли и ознакомления с ними
участников рынка.
Пример построен на описании
ключевого модуля этой системы – торгового терминала, который позволяет трейдеру
в реальном времени отслеживать ход текущей торговой сессии (поступающие заявки
на покупку и продажу контрактов, котировки контрактов и т.п.) и выставлять на
торги свои заявки. Внешний вид терминала представлен на рисунке.
Терминал представляет собой
окно броузера (главное окно приложения), поделенное на два вложенных фреймовых
окна. В левом окне (управляющей панели) выводится список торгуемых инструментов
(контрактов). С помощью checkbox’ов в этой панели трейдер может выбрать
инструменты, с которыми он собирается работать.В правом окне фрейма (рабочей
панели) отображаются мини-окна, представляющие информацию по состоянию торгов
выбранного инструмента. Мини-окна инструментов реализованы при помощи обычных html-элементов <div>. Трейдер может свободно перемещать мини-окна
внутри рабочей панели и управлять их размерами (шириной и высотой). Каждое из
мини-окон можно закрыть, либо нажав на нем кнопку [х], либо сняв отметку в checkbox’е
соответствующего инструмента в управляющей панели. При завершении работы с
торговым терминалом в хранилище броузера (persist store или cookies) сохраняется его визуальное состояние: список
всех торгуемых в текущей сессии инструментов; список инструментов, отображаемых
в мини-окнах; местоположение и размеры мини-окон и пр.
Соответственно, при очередной
загрузке главного окна терминала извлекается сохраненная на стороне броузера
информация о предыдущем визуальном состоянии терминала. После этого главное
окно параллельно инициирует три асинхронных запроса к серверу, первый из
которых запрашивает начальную порцию данных (в xml-формате), куда должен войти текущий список всех
торгуемых инструментов, а также данные, которыми следует наполнить открытые
мини-окна. Два других запроса направлены на то, чтобы получить с Веб-сервера
документы, которые должны быть размещены в левом и правом фреймовых окнах.
Особенность инициализации
терминала заключается в том, что порядок, в котором отрабатываются эти запросы,
может быть самым произвольным и промежуток времени от завершения первого из них
до выполнения последнего может быть большим (до 10 секунд и даже больше). Это
зависит от объема передаваемых с сервера данных: если данных мало (особенно
когда это первая сессия трейдера), то первый запрос скорее всего выполнится
раньше остальных. Однако более типична ситуация, когда данные будут получены с
большой задержкой по отношению к инициализации фреймовых окон. Нельзя исключать
и тот случай, когда данные вклинятся между фреймовыми окнами.
С учетом отмеченных
особенностей загрузки системы, опишем проблему обеспечения дружелюбного
поведения этого интерфейса на этапе инициализации, которую фактически можно
считать модельной задачей для применения нашего инструментария. Дружелюбность
интерфейса при инициализации предполагает, что система не должна блокировать
работу панелей терминала, которые успели загрузиться, даже если мы еще не
получили данные от начальной загрузки. Другими словами, мы хотим, чтобы трейдер
имел возможность, пока данные по текущей торговой сессии не поступили, выбирать инструменты, которые будут
интересовать его сегодня, подвигать мини-окна и т.п. Это вполне актуально по крайней
мере для трейдеров, работающих с короткими однодневными контрактами.
Реализация такого поведения
системы на фазе инициализации представляет собой настоящую головоломку, которая
однако легко решается с помощью предложенного инструментария. Для этого на
начальной фазе инициализации, после того, как из хранилища броузера прочитаны
данные о предыдущем визуальном состоянии терминала, означим переменную
приложения $arrInstruments полученным списком инструментов
['WWSCE44-050',...]. Дополнительно для каждого инструмента из этого списка
введем переменную, которая принимает значение true, если инструмент выбран, и false в противном случае.
Например, для инструмента с кодом 'WWSCE44-050' используем переменную $opened_WWSCE44-050' и,
если этот инструмент был выбран, воспользуемся вызовом функции appvarSet(‘$opened_WWSCE44-050', true).
В момент, когда будет получена
начальная порция данных, переменная $arrInstruments будет означена массивом торгуемых сегодня
инструментов. Однако работа левой и правой панелей должна быть рассчитана как
на то, что функционирование терминала начинается до получения данных, так и на
то, что данные поступят уже в процессе работы. Для этого в левом окне создадим
триггер, который должен синхронизировать номенклатуру инструментов,
отображенную в панели, и текущее значение переменной $arrInstruments.
<script type=”text/js-trigger”>
for(eInstr in instrumentMenu)
if(!is_in_array(eInstr.instr_id,$arrInstrument))
removeMenuItem(eInstr);
for(i
in $arrInstrument)
if(!existsMenuItem($arrInstrument[i]))
addMenuItem($arrInstrument[i]);
</script>
Аналогичным образом можно
добавить триггер в рабочую панель, который будет поддерживать соответствующий
текущему списку инструментов набор мини-окон.
Теперь перейдем к реализации
операций добавления и удаления инструментов в меню управляющей панели (функции addMenuItem и removeMenuItem). Операция
добавления связана со вводом в обращение новых инструментов, а операция
удаления касается инструментов, исключенных из торгов. Здесь возникает задача
синхронизации между компонентами левой и правой панели, поскольку инструмент
может быть закрыт не только с помощью checkbox’а, но также по кнопке [х] в мини-окне рабочей
панели; кроме того надо синхронизовать «видимость» мини-окон с состоянием checkbox’ов. При этом
следует помнить, что правая и левая панель загружаются асинхронно.
Для решения указанных задач у
нас фактически все есть, поскольку введены переменные приложения, отражающие
текущее состояние «выбранности» инструментов. Для актуализации значений этих
переменных с элементом checkbox, создаваемым в ходе выполнения функции addMenuItem, следует
привязать к этому элементу обработчик:
<script for=”checkbox_WWSCE44-050” event=”onclick”>
appvarSet(‘$opened_WWSCE44-050',
window.event.srcElement.checked);
</script>
Кроме того, из функции addMenuItem следует
породить динамический триггер, который будет реагировать на изменение значения
переменной $opened_WWSCE44-050 и выставлять свойство checkbox.checked в соответствующее значение (например, если
инструмент исключается из выбранных путем закрытия мини-окна в рабочей панели,
то свойство checkbox.checked
примет значение false и в управляющей панели с инструмента снимется отметка):
createTrigger(”trigger_WWSCE44-050”,
‘var
eChk = window.document.’+
‘getElementById(”checkbox_WWSCE44-050”);\n’+
‘ eChk.checked = $opened_WWSCE44-050;’);
При реализации функции removeMenuItem, удаляющей
инструмент из меню, надо не забыть удалить все созданные динамические фрагменты
JavaScript’а,
т.е. обработчик события onclick и триггер. Примерно по той же схеме могут быть
реализованы операции создания и удаления мини-окон в рабочей панели, и здесь
тоже целесообразно использовать динамические триггеры, чтобы синхронизовать
визуализацию мини-окна с состоянием соответствующей переменной приложения.
Терминал производит
циклическое опрашивание сервера в течение всего сеанса своей работы, получая
информацию о тех изменениях, которые он должен визуализировать на экране. Хотя
при описании приведенной реализации мы рассматривали только ситуацию начальной
загрузки данных, стоит отметить, что эта реализация будет корректно
отрабатывать и те изменения, которые
происходят в ходе текущей торговой сессии при порождении или исключении
инструментов, т.е. она позволяет обслужить весь рабочий цикл системы.
В настоящей работе рассмотрены
технологические аспекты создания Веб-приложений нового поколения (Web 2.0),
обладающих полноценным, насыщенным пользовательским интерфейсом, который по
своей выразительности, функциональным возможностям и удобству близок к
традиционным «настольным» интерфейсам. Исследованы особенности и проблемы,
связанные с синхронизацией состояния DHTM-компонент, расположенных в разных
окнах приложения, при синхронном и асинхронном режиме обмена данными между
клиентом и Веб-сервером через Интернет. Показано принципиальное отличие
асинхронного режима в среде Веб от его использования в традиционных
«настольных» клиент-серверных системах.
Центральной темой работы
является разработка методов для организации взаимодействия программных DHTM-компонент
клиентского Веб-приложения. На основе исследования схем взаимодействия
компонент, предлагаемых современными архитектурами Веб-разработки, показана
перспективность использования парадигмы MVC для организации взаимодействия
компонент в задачах построения
клиентской архитектуры Веб-приложений с насыщенным пользовательским
интерфейсом. На базе таких фундаментальных свойств синхронизации по схеме MVC,
как опосредованность и статусность, нами были реализованы программные средства,
которые позволяют разработчику Веб-приложений легко и удобно использовать
MVC-подобные методы опосредованного статусного взаимодействия DHTML-компонент
для синхронизации их совместного поведения. Полученная при этом архитектура
Веб-разработки является достаточно универсальной и может быть использована в
качестве хорошей начальной основы для создания полнофункционального пакета для
поддержки разработки AJAX-приложений.
[1] Tim O'Reilly, “What Is Web 2.0”
http://www.oreillynet.com/pub/a/oreilly/tim/news/2005/09/30/what-is-web-20.html
[2] Jesse James Garrett,
“Ajax: A New Approach to Web Applications” essay
February 18, 2005
http://www.adaptivepath.com/publications/essays/archives/000385.php
[3] World Wide World
Consortium Document Object Model DOM.
http:// www.w3.org/DOM/
[4] Cascading Style Sheets,
http://www.w3.org/Style/CSS/
[5] ECMAScript Language
Specification. Standard ECMA-262.
http://www.ecma.ch/ecma1/stand/ecma-262.htm.
[6] Google Gmail,
http://www.gmail.com/
[7] Google Maps,
http://maps.google.com/
[8] Flickr, Photo Sharing Application http://www.flickr.com
[9]
http://ajaxian.com/resources/
[10]
http://ajaxpatterns.org/Ajax_Frameworks
[11] Steve Burbeck,
“Applications Programming in Smalltalk-80(TM): How to use Model-View-Controller(MVC)”,
http://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html
[12] G. Krasner and S. Pope. “A description of the model-view-controller user
interface paradigm in the smalltalk-80 system”. Journal of Object Oriented
Programming, 1(3):2649, 1988
[13] A. J. Dix, “Status and
events: static and dynamic properties of interactive systems.” Proceedings of
the Eurographics Seminar: Formal Methods in Computer Graphics, Ed. D. A. Duce.
Marina di Carrara, Italy, 1991.
[14] G.D.Abowd and A.J.Dix.
“Integrating status and event phenomena in formal specifications of interactive
systems.” In Proc. of the ACM SIGSOFT'94 Symposium on the Foundations of
Software Engineering, New Orleans, Louisiana, December 1994.
[15] JFC/Swing,
http://java.sun.com/products/jfc/
[16]Amy Fowler, “A Swing Architecture Overview”
http://java.sun.com/products/jfc/tsc/articles/architecture/
[17] Ali Mesbah, K. Broenink and Arie van Deursen.
«SPIAR: an architectural style for single page internet applications.»,
Technical Report SEN-R0603, CWI, 2006.
[18] "The XMLHttpRequest
Object", W3C Working Draft 19 June 2006
http://www.w3.org/TR/XMLHttpRequest/
[19] W3C Web APIs Working Group
http://www.w3.org/2006/webapi/
|