Конечные автоматы в JavaScript. Часть 3: Тестируем виджет (исходники)

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

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

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

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

Выполнение приложений в браузерах

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

  • Netscape Navigator 8.1;
  • Microsoft Internet Explorer 6.0;
  • Opera 9.0;
  • Mozilla Firefox 1.5.

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

Упрощенная среда тестирования

Протестировать нашу реализацию можно с помощью некоторого фрагмента программного кода, встроенного в HTML-код Web-страницы. Код должен создать объекты FadingTooltip с помощью конструктора и выполнить их привязку к HTML -элементам. Это несложно выполнить при помощи функции, определенной в заголовке (head) HTML Web-страницы, и использующей атрибуты id HTML-элементов, как показано в листинге 1.

Листинг 1. Код JavaScript для создания виджетов FadingTooltip

                
<head>
    ...
    <script src='FadingTooltip.js' content='text/javascript'></script>
    <script content='text/javascript'>
        function createFadingTooltip(id, content, parameters) {
            new FadingTooltip(document.getElementById(id), content, parameters);
        }
    </script>
    ...
</head>

Аргументами для функции createFadingTooltip служат идентификатор HTML-элемента, содержание подсказки и необязательный набор параметров. Функция просто преобразует идентификатор элемента в указатель, а затем вызывает конструктор, передавая остальную часть аргумента без изменений. Указатели на объекты, возвращаемые конструктором, не учитываются, поскольку конструктор заключает указатели на объекты в определяемые им функции событий, как описано в разделе Части 2 Перехват событий курсора.

Затем нам понадобятся некоторые HTML-элементы с атрибутами id, определенные в HTML-элементе body Web-страницы, как показано в листинге 2.

Листинг 2. HTML-код для некоторых HTML-элементов

                
<body>
    ...
    <p>Следующие элементы имеют подсказки, заданные с помощью виджета FadingTooltip:
    <div id='tests' class='TestStyle'>
        Это несколько <span id='TestLabel'>усовершенствованных подсказок</span>: 
        <input type='text' id='TestInput' size=25>
        <input type='button' id='TestButton' value='Press this button'>
    </div>
    ...

Наконец, нам нужен некоторый код для вызова функции createFadingTooltip с подходящими аргументами для каждого HTML-элемента, как в листинге 3.

Листинг 3. Код JavaScript для привязки виджетов FadingTooltip к HTML-элементам

                
<body>
    ...
    <script content='text/javascript'>
        createFadingTooltip('TestLabel', 
                         'Переместите курсор немного вправо ...');
        createFadingTooltip('TestInput', 
                         'Введите, <i>пожалуйста</i>:' +
                         '<ul compact style='margin-top: 0; margin-bottom: 0'>' +
                         '<li>номер вашего банковского счета' + 
                         '<li> PIN-код' +
                         '</ul>' +
                         '<i>Заранее</i> спасибо.', 
                         { fadeinTime: 3,
                           fadeoutTime: 3 } );
        createFadingTooltip('TestButton', 
                         '<img src='smiley.gif' align='absmiddle'>' +
                          '<big>Продолжим.</big> ' +
                          'Нажмите здесь. ' +
                          '<small>Что может случиться? <small>
                           Верьте мне.</small></small>', 
                          { tooltipOpacity: 1, 
                            tooltipClass: 'AnotherTestStyle',
                            pauseTime: 2,
                            fadeinTime: 0.5, 
                            displayTime: 0,
                           fadeoutTime: 10,
                            trace: true } );
    /script>
    ...

Первая подсказка (для HTML-элемента, определенного как TestLabel), создается с самым простым аргументом: она содержит только текст, аргументы параметров опущены, поэтому для всех параметров будут использованы значения по умолчанию. Вторая подсказка (для HTML-элемента TestInput), форматирует содержимое и задает время появления и исчезновения подсказки (в секундах). Третья подсказка (для HTML-элемента TestButton) содержит изображение и форматированный контент и задает дополнительные параметры, в том числе, класс каскадных таблиц стилей (Cascading Stylesheet, CSS) для оформления подсказки стилем.

Mozilla Firefox - это самый новый и передовой из доступных браузеров, он более тесно связан с открытыми стандартами, чем другие браузеры, поэтому мы начнем именно с него. (Если вы используете Microsoft Internet Explorer, то, возможно, имеет смысл сначала прочитать следующие два раздела, о непредусмотренных ситуациях и ошибке постепенного появления/исчезновения подсказки, а затем вернуться к этому разделу.)

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

Вам решать, действительно ли эти отличия являются улучшениями, и стоит ли тратить время и усилия на разработку собственных виджетов-подсказок. Если да, то нужно скопировать исходные файлы для реализации и среду тестирования на жесткий диск, чтобы можно было изменять параметры, стили или код (см. раздел Загрузка). Для этого вам не нужен собственный Web-сервер, просто используйте файл:://... URL для загрузки среды тестирования с внесенными изменениями в браузер.

Если случаются непредусмотренные события

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

Рисунок 1. Непредусмотренное событие " mousemove" в Internet Explorer
Непредусмотренное событие "mousemove" в Internet Explorer

Обратимся к таблице состояний из раздела Заполняем таблицу состояний Части 1, которая показана на рисунке 2. Вспомните, мы не предполагали наступления событий mousemove в неактивном состоянии, поэтому оставили незаполненной соответствующую ячейку в таблице actionTransitionFunctions раздела Создание таблицы действий/переходов в Части 2.

Рисунок 2.Первоначальная таблица состояний для виджета FadingTooltip
Первоначальная таблица состояний для виджета FadingTooltip

Интуитивно мы понимаем, что событие mouseover должно предшествовать событию mousemove и вызвать переход виджета в состояние Pause, в котором предполагается наступление событий mousemove (см. Рисунок 5 в Части 1). Очевидно, что браузер Firefox работает в соответствии с нашими интуитивными ожиданиями, а Internet Explorer - нет. Возможно, это обусловлено ошибкой в реализации конечного автомата, генерирующего события курсора в Internet Explorer, или тем, что этот браузер не отслеживает свои внутренние состояния. В любом случае необходимо адаптировать нашу реализацию конечного автомата к ситуации.

К счастью, дисциплина обслуживания, связанная с применением шаблона конечного автомата, теперь позволяет с легкостью вносить подобные изменения. Подумайте, какое поведение виджета будет подходящим, если событие mousemove произойдет в состоянии Inactive: действия и переход будут теми же, что и для события mouseover в состоянии Inactive. Вернемся к нашей таблице actionTransitionFunctions и вспомним, что метод doActionTransition позволяет любой функции из этой таблицы дублировать действия и переходы другой функции. С помощью этого метода можно задать обработку нашего непредусмотренного события, добавив функцию, которая выделена полужирным шрифтом в листинге 4, в таблицу actionTransitionFunctions.

Листинг 4. Код JavaScript для обработки непредусмотренного события mousemove в состоянии Inactive

                
FadingTooltip.prototype = { 
    ...
    actionTransitionFunctions: { 
    ...
    Inactive: {
          mousemove: function(event) {
                return this.doActionTransition('Inactive', 'mouseover', event);
                },
       ...

Продолжая тестирование виджета в Internet Explorer, мы вскоре столкнемся с еще одной аналогичной непредусмотренной ситуацией, показанной на рисунке 3.

Рисунок 3. Непредусмотренное событие " mouseout" в Internet Explorer
Непредусмотренное событие "mouseout" в Internet Explorer

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

Листинг 5. Код JavaScript для обработки непредусмотренного события mouseout в состоянии Inactive

                
FadingTooltip.prototype = { 
    ...
    actionTransitionFunctions: { 
    ...
       Inactive: {
                mouseout: function(event) {
                return this.currentState; // do nothing
                },
      ...

На проектном этапе мы не предполагали, что события mousemove или mouseout будут происходить в состоянии Inactive. Но прежде, чем критиковать Microsoft за недостатки обработки событий mouseover в браузере, представим себе, что происходит в подобной ситуации в любом браузере: курсор перемещается к HTML-элементу и останавливается над ним на время, достаточное для того, чтобы виджет FadingTooltip мог постепенно появиться, ненадолго задержаться на экране, а затем постепенно исчезнуть. При этом конечный автомат проходит весь цикл своих состояний, возвращаясь в состояние Inactive, пока курсор остается над HTML-элементом. Когда после этого курсор начинает смещаться с HTML-элемента, любой браузер генерирует событие mousemove или mouseout в состоянии Inactive. Это происходит практически во всех браузерах, включая и Firefox. Независимо от любых возможных ошибок в Internet Explorer, это ошибка проекта конечного автомата, что показано на рисунке 4.

Рисунок 4. Непредусмотренное событие " mousemove" в Firefox
Непредусмотренное событие "mousemove" в Firefox

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

Но, к сожалению, при продолжении тестирования виджета после внесения изменений обнаруживается, что в Internet Explorer возникает еще одна непредвиденная ситуация: непредусмотренное событие " mouseover" в состоянии Pause.

Поскольку события mouseover в состоянии Pause требуют тех же действий и переходов, что и события mouseover в состоянии Inactive, можно добиться корректной обработки этой ситуации посредством вызова метода doActionTransaction. Однако не существует какой-либо обоснованной последовательности событий (которую можно себе представить), способной привести другие браузеры к подобной ситуации, поэтому давайте внесем в проект изменения только для Internet Explorer, как показано в листинге 6.

Листинг 6. Код JavaScript для обработки непредусмотренного события только в Internet Explorer

                
FadingTooltip.prototype = { 
    ...
    actionTransitionFunctions: { 
        ...
    },
    ...
};
if ( (window.navigator.userAgent).indexOf('MSIE')!=-1 ) {
                FadingTooltip.prototype.actionTransitionFunctions["Pause"]["mouseover"] = 
                function(event) { return 
                this.doActionTransition('Inactive', 'mouseover', event);
                };
                }
            

Если браузер представляет собой одну из версий Internet Explorer, измените таблицу actionTransitionFunctions прототипа FadingTooltips после его определения, но до начала использования виджета, так, чтобы события mouseover в состоянии Pause обрабатывались так же, как и события mouseover в состоянии Inactive. Не забывайте, что в JavaScript ассоциативные матрицы и объекты эквивалентны, поэтому для изменения таблицы можно использовать любую из двух нотаций.

Если постепенно появляющиеся/исчезающие подсказки появляются/исчезают не постепенно

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

Internet Explorer не поддерживает нестандартный стиль с именем filter , который имеет аналогичную функцию. Чтобы можно было его использовать, придется изменить метод createTooltip, вставив в код строки, выделенные полужирным шрифтом в листинге 7.

Листинг 7. Дополнение к коду JavaScript для создания подсказки в Internet Explorer

                
FadingTooltip.prototype = { 
    ...
    createTooltip: function() {  
    this.tooltipDivision = document.createElement('div');
        ...   
        this.currentOpacity = this.tooltipDivision.style.opacity = 0;
       if (this.tooltipDivision.filters) { // for MSIE only
                this.tooltipDivision.style.filter = 'alpha(opacity=0)';
                }
        ...
    },	
    ... 

Необходимо также соответствующее дополнение в методе fadeTooltip, показанное в листинге 8.

Листинг 8. Дополнение к коду JavaScript для выполнения постепенного появления/исчезновения подсказки в Internet Explorer FadingTooltip

                .prototype = { 
    ...
    fadeTooltip: function(opacityDelta) { 
        ...
        this.tooltipDivision.style.opacity = this.currentOpacity;
       if (this.tooltipDivision.filters) { // for MSIE only
                this.tooltipDivision.filters.item('alpha').opacity = 
                100*this.currentOpacity;
                }
    },	
    ... 

Может показаться,что Opera известна как браузер не такому широкому кругу пользователей, тем не менее, этот браузер высоко ценится в технических кругах. К сожалению, разработчики Opera довольно поздно включили в свой браузер поддержку стиля для непрозрачности, предлагаемого CSS, поэтому виджеты FadingTooltip будут появляться и исчезать внезапно, а не постепенно во всех версиях этого браузера вплоть до 9. В отличие от Internet Explorer, в предыдущих версиях Opera не существует альтернативного синтаксиса для описания прозрачности, поэтому единственное решение проблемы - это модернизация до текущей версии.

Еще немного о производительности

Теперь, когда наш виджет FadingTooltip стабильно работает во всех популярных браузерах, самое время выяснить, насколько мы близки к основной цели обеспечения производительности - минимальному использованию ресурсов процессора. Самый простой способ узнать это в среде Windows - понаблюдать за вкладкой Performance (Производительность) Task Manager (Диспетчера задач) Windows в процессе выполнения анимации виджета.

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

Рисунок 5. Фоновая активность программ в Диспетчере задач Windows
Фоновая активность программ в Диспетчере задач Windows

Перед запуском виджета FadingTooltip можно остановить фоновую деятельность части программ, чтобы она не мешала оценить данные об использовании процессора виджетом. Большинство программ, отображающих значки в системном лотке Windows, можно закрыть через их контекстное меню. Другие программы, выполняющиеся в фоновом режиме, можно закрыть через апплет Services (Службы) Панели управления Windows.

На системе с процессором Intel Pentium-III с тактовой частотой 1,1 ГГц, находящейся в состоянии бездействия, использование ресурсов процессора виджетом FadingTooltip действительно должно быть пренебрежительно малым, как показано на рисунке 6.

Рисунок 6. Активность анимации виджета в Диспетчере задач Windows
Активность анимации виджета в Диспетчере задач Windows

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

Не забудьте внести сделанные изменения в проектную документацию

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

Рисунок 7. Таблица состояний для виджета FadingTooltip по итогам тестирования
Таблица состояний для виджета FadingTooltip по итогам тестирования

Как показано на рисунке 8, ничуть не сложнее внести изменения в граф состояний

Рисунок 8. Граф состояний для виджета FadingTooltip по итогам тестирования
Граф состояний для виджета FadingTooltip по итогам тестирования

Строки, которые мы вставили в методы createTooltip и fadeTooltip для адаптации способа задания стиля непрозрачности в Internet Explorer, нельзя назвать изменениями на уровне проекта. Эти изменения документируются в комментариях к исходному коду.

Дальнейшая разработка

Цель этой серии статей - продемонстрировать применение шаблона проекта конечного автомата к приложениям на базе браузера и использование двух характерных особенностей языка JavaScript в процессе создания красивой и эффективной программы. В результате мы разработали виджет FadingTooltip в виде отдельного самодостаточного объекта JavaScript. Код получился легким, но не особенно гибким. В завершение я хочу указать некоторые возможные направления для дальнейших разработок.

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

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

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

  • Расширение инфраструктуры конечных автоматов за счет включения перехвата событий клавиатуры и сетевых запросов;
  • Графических или табличных представлений грамматик формальных языков и сетевых протоколов;
  • Действий по проверке корректности текстовых значений и управлению сетевыми сеансами.

Другие программы, выполняющиеся под управлением того же процессора, могут являться источниками событий и производить действия, которые могут использовать программы, выполняющиеся в браузерах. Например, приложение для общения голосом посредством-IP-телефонии может генерировать события ring-in (входящий звонок), ring-out (исходящий звонок), connect (соединение), и disconnect (разрыв соединения) и выполнять действия call (звонок), hold (снять трубку) и hang-up (повесить трубку) . Еще один из возможных путей разработки может быть связан с расширением инфраструктуры за счет включения методов для перехвата событий в других приложениях и выполнения в них действий. Вам могут понадобиться подключаемые модули Java™ для того, чтобы сделать события и действия в других процессах доступными для конечных автоматов, выполняющихся в браузерах. На этом вполне можно закончить статью, поскольку JavaScript начинался как язык сценариев для подключаемых модулей Java.


Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=7867