Кінцеві автомати в JavaScript. Частина 2: Реалізація віджета (исходники), Різне, Програмування, статті

Кінцеві автомати є організуючим принципом для тих розробників, які проектують і реалізують складну поведінку в програмах, керованих подіями, наприклад, в мережевих адаптерах і компіляторах. А тепер програмовані web-браузери відкрили нову керовану подіями середовище для нового покоління додатків. Оскільки програми на базі браузера, популярність яких зросла з появою технологій Ajax, стали більш складними, ви можете отримати перевагу, якщо скористаєтеся дисципліною розробки та структурою, які пропонують кінцеві автомати.


В частини 1 розповідається про віджеті підказки для web-сторінок; такі підказки демонструють більш досконале поведінка, ніж вбудовані підказки, що надаються популярними web-браузерами. Цей віджет FadingTooltip поступово виводить відображення підказки в полі зору після того, як курсор зупиниться над HTML-елементом, а потім плавно видаляє її після того, як вона деякий час побуде на екрані. Підказка слід за переміщеннями курсора навіть у ті моменти, коли її видимість поступово наростає або знижується, а ефект зникнення / появи змінює напрямок, коли курсор зміщується з HTML-елемента або повертається в його межі. Така поведінка вимагає від віджета FadingTooltip реакції на безліч різних подій, а в деяких випадках певної реакції на конкретну подію в залежності від попереднього події.


Розробники давно використовують шаблони проектування кінцевих автоматів для організації поведінки керованих подіями програм, подібних щойно описаної. У частині 1 ми застосували цю дисципліну розробки для створення таблиці станів, яка визначає бажане поведінку, як показано на малюнку 1.


Рисунок 1. Таблиця станів віджету FadingTooltip

Рядки та стовпці таблиці станів озаглавлені іменами подій, на які буде реагувати віджет, і іменами станів, в яких віджет буде очікувати між подіями. Кожна комірка таблиці визначає дії, які виконає віджет при настанні конкретного події в конкретному стані. В осередках таблиці можна також задати наступне стан, у який перейде віджет після виконання дії, або визначити, що віджет залишиться в колишньому стані. Порожні комірки таблиці показують, що в даних станах дані події не наступають. Крім того, в частині 1 ми склали список змінних стану, які віджет повинен запам’ятовувати між подіями, щоб він міг виконувати пов’язані дії, зазначені в різних осередках.







 



У частині 3 ми виконаємо тестування реалізації віджета в популярних браузерах і розглянемо на практиці ситуації, в яких подія “не має наступити”.
 

У частині 1 цієї серії статей стверджувалося, що JavaScript прекрасно підходить в якості середовища виконання для кінцевих автоматів, і були згадані деякі можливості, які мають значення на стадії розробки. У цій статті ми на практиці розберемо деталі перекладу нашого проекту на мову JavaScript, використовуючи деякі чудові особливості мови і пристосовуючись до деяких незручним деталей, які ускладнюють реалізацію програми.


Переклад проекту на JavaScript


Закінчивши розробку проекту кінцевого автомата в частині 1, ми підготувалися до реалізації віджету FadingTooltip на мові JavaScript. Саме тут від простих абстракцій на етапі проектування розробник переходить до суворої дійсності реального середовища виконання.


Необхідно вибирати тільки самі останні версії найбільш популярних браузерів: Netscape Navigator, Microsoft ® Internet Explorer ®, Opera і Mozilla Firefox. Навіть цей обмежений набір середовищ виконання забезпечить вам достатньо проблем. Ви зустрінетесь з деталями реального перехоплення подій миші і таймера в різних браузерах програмою на JavaScript. Елегантна особливість мови JavaScript під назвою замикання функцій прийде до нас на допомогу. Ми скористаємося ще однією чудовою особливістю мови JavaScript, асоціативними масивами, для перекладу нашої таблиці станів безпосередньо в програмний код. І, нарешті, ви побачите, як створити підказку і призначити їй певний стиль за допомогою HTML-елемента Division, додати в неї текст і зображення, помістити поряд з курсором, плавно нарощуючи і послаблюючи її видимість, а також навчити її йти за переміщеннями курсора.


Але для початку, в кращих традиціях об’єктно-орієнтованого програмування, нам потрібен об’єкт, який вміщатиме всі інші деталі реалізації, тому ми почнемо саме з нього.


Об’єкт, в якому розміщуватиметься все інше


Віджет FadingTooltip зажадає більш складних дій при програмуванні, ніж звичайні короткі фрагменти коду JavaScript, які web-дизайнери часто вирізають і вставляють в HTML-сторінки. Інженерам по програмного забезпечення буде комфортно працювати з ідеєю угруповання змінних і методів віджета в об’єкті, хоча об’єктна модель JavaScript може здатися трохи незвичайною тим із них, хто вивчав програмування на Java ™ і С + +. Об’єкт JavaScript цілком задовольняє нашим вимогам: він дозволяє згрупувати змінні і методи в один об’єкт, а потім створити окремі екземпляри даних для кожної підказки. Екземпляри об’єкту будуть використовувати загальний код та виконуватися незалежно один від одного.


У JavaScript об’єкт конструктор – це просто функція; причому ім’я функції є ім’ям об’єкта. Нашому віджету необхідно буде знати, в якій HTML-елемент вбудовуватися і яке вміст відображати в підказкою для нього, тому ми поставимо ці аргументи в конструкторі і збережемо їх в об’єкті. (Нам також потрібно спосіб завдання параметрів, пов’язаних з поведінкою і відображенням підказки, тому ми також визначимо аргумент для цих цілей і використовуємо його далі в цій статті.) Змінні мови нетипізовані, тому конструктор об’єкта може починатися з коду, показаного в лістингу 1.


Лістинг 1. Код JavaScript для конструктора об’єкту FadingTooltip





function FadingTooltip(htmlElement, tooltipContent, parameters) { this.htmlElement = htmlElement; / / зберігає покажчик на HTML-елемент, подія / / Миші для якого зчеплене з цим об’єктом this.tooltipContent = tooltipContent; / / зберігає текст і теги для / / HTML-елемента Division


В JavaScript можна додавати властивості об’єкта, які можуть бути змінними або методами, до об’єкта або при його створенні, або в будь-який час після цього, просто задавши для них значення, оскільки конструктор виконує для властивостей методи this.htmlElement і this.tooltipContent.


У JavaScript прототип об’єкта – це шаблон для створення нового екземпляра об’єкта; він визначає вихідні властивості об’єкта і їх початкові значення. Почнемо створювати прототип нашого об’єкта з змінних стани, які ми визначили в частині 1 цієї серії і які необхідні нашому віджету, як показано в лістингу 2.


Лістинг 2. Код JavaScript для прототипу об’єкта FadingTooltip





FadingTooltip.prototype = { currentState: null, / / ​​поточний стан кінцевого автомата (одне з / / Імен станів, наведених у таблиці) currentTimer: null, / / ​​значення, що повертається методом setTimeout, / / Не дорівнює нулю, якщо виконується таймер currentTicker: null, / / ​​повертається методом setInterval, не дорівнює нулю, / / Якщо виконується тікер currentOpacity: 0.0, / / ​​поточна непрозорість підказки, значення / / Від 0.0 до 1.0 включно tooltipDivision: null, / / ​​покажчик на HTML-елемент division при видимій підказкою lastCursorX: 0, / / ​​координата x-курсора в момент самого останнього події миші lastCursorY: 0, / / ​​координата y-курсора в момент самого останнього події миші


Прототип об’єкта – гарне місце для визначення практично будь-яких властивостей, які мають отноошеніе до кінцевого автомату: таблиці станів, її дій і параметрів дій. Залишилося зовсім небагато – виконати прив’язку до подій курсора – щоб закінчити створення конструктора об’єкту, а інша частина статті буде присвячена роботі з прототипом об’єкта.


Прив’язка (перехоплення) подій курсора


Як згадувалося при розгляді проектного етапу в частині 1, браузери можуть передавати події коду JavaScript, коли курсор затримується над, переміщається в межах або зміщується з HTML-елемента. Ці події містять таку корисну інформацію, як тип події та актуальна позиція курсора на сторінці. Браузери передають події за допомогою виклику функцій, які були попередньо зареєстровані. На жаль, способи реєстрації цих функцій і передачі аргументів в браузерах різні. Щоб забезпечити перехоплення подій курсора в кінцевому автоматі у всіх популярних браузерах, необхідно реалізувати три різних моделі подій. На щастя, програмний код для кожної моделі подій досить компактний. На жаль, компактність цього коду не виправдовує його складності.


Mozilla Firefox, Opera і останні версії Netscape Navigator підтримують стандартизовану модель подій, запропоновану консорціумом World Wide Web (W3C). Це буде вашим першим вибором через простоту реєстрації (і скасування реєстрації) функцій подій і через те, що зчеплення декількох зареєстрованих функцій обробляється браузером. Якщо доступно, можна перехоплювати події курсора допомогою виклику методу addEventListener HTML-елемента, передаючи тип події та інформацію про те, яку функцію слід викликати при настанні цієї події для даного HTML-елемента, як показано в лістингу 3.


Лістинг 3. Код JavaScript для перехоплення подій курсора





function FadingTooltip(htmlElement, tooltipContent, parameters) {

htmlElement.fadingTooltip = this;
if (htmlElement.addEventListener) { // for FF and NS and Opera
htmlElement.addEventListener(
“mouseover”,
function(event) { this.fadingTooltip.handleEvent(event); },
false);
htmlElement.addEventListener(
“mousemove”,
function(event) { this.fadingTooltip.handleEvent(event); },
false);
htmlElement.addEventListener(
“mouseout”,
function(event) { this.fadingTooltip.handleEvent(event); },
false);
}


Другим аргументом для викликів addEventListener будуть анонімні функції, то є такі функції, які не мають імен. Це перша можливість визначити функції в інших функціях в мові JavaScript, але не остання, тому давайте займемося нею зараз. Функцію function можна використовувати в будь-якому місці коду JavaScript, щоб задати опис анонімної функції по ходу роботи. Ця функція повертає вказівник на функцію, яка згодом може використовуватися як будь інше посилальне значення. У нашому віджеті FadingTooltip ми передамо покажчик функції іншої функції в якості аргументу, перевіримо їх на null, Привласнимо змінним і оголосимо в якості методів об’єкта.


Схоже, що анонімні функції, передані методу addEventListener , Роблять не занадто багато. При настанні події курсора браузер викличе їх, передаючи об’єкт event, Який вони, в свою чергу, передадуть методу handleEvent об’єкта FadingTooltip. Об’єкти подій браузера містять тип події та інформацію про становище курсора, тому метод handleEvent може обробляти всі події курсора, на які має реагувати віджет.


Ці нескладні анонімні функції, крім того, виконують ще одну важливу, але непомітну задачу. У моделі події W3C функції, зареєстровані за допомогою методу addEventListener HTML-елемента, стають методами цього елемента, тому при виклику їх браузером вбудована мінлива this вказує на цей HTML-елемент. Але метод handleEvent потребує покажчику на об’єкт FadingTooltip, який містить наші змінні стану. Один із способів вказати йому цей об’єкт – це додати властивість fadingTooltip в HTML-елемент, який вкаже на об’єкт FadingTooltip, а потім перейде до нього, щоб викликати метод handleEvent нашого об’єкта. Завдяки цьому, this вказує на об’єкт FadingTooltip при виконанні методу handleEvent.


Перехоплення подій курсора в Internet Explorer


Microsoft Internet Explorer в даний час не підтримує запропоновану W3C стандартну модель подій, а пропонує власну просту модель. До цих відмінностей, перелічених нижче, нескладно пристосуватися:


Якщо це доступно, можна перехоплювати події за допомогою виклику методу attachEvent HTML-елемента, передаючи йому трохи відрізняються типи подій і функції, як показано в лістингу 4.

Це ваша перша можливість використовувати замикання функцій для включення змінних в визначення функцій, але також не остання, тому давайте розглянемо і цей варіант.


Лістинг 4. Код JavaScript для перехоплення подій курсора в Internet Explorer





function FadingTooltip(htmlElement, tooltipContent, parameters) {

else if (htmlElement.attachEvent) { // for MSIE
htmlElement.attachEvent(
“onmouseover”,
function() { htmlElement.fadingTooltip.handleEvent(window.event); } );
htmlElement.attachEvent(
“onmousemove”,
function() { htmlElement.fadingTooltip.handleEvent(window.event); } );
htmlElement.attachEvent(
“onmouseout”,
function() { htmlElement.fadingTooltip.handleEvent(window.event); } );
}


Функції, зареєстровані за допомогою методу attachEvent HTML-елемента, не стають методом цього елемента. Коли настане подія курсора, браузер викличе цей метод, але вбудована мінлива this вкаже на глобальний об’єкт window, А не на HTML-елемент, тому наші функції не зможуть знайти об’єкт FadingTooltip за вказівником, збереженому в HTML-елементі.


На щастя, визначення анонімної функції входять в лексичний контекст аргументу htmlElement в конструкторі об’єкта. За допомогою простого використання змінної htmlElement в описі анонімної функції можна включити його в такі функції. Це називається замиканням функції: якщо функція описана всередині іншої функції, причому внутрішня функція використовує локальні змінні зовнішньої функції, то JavaScript зберігає такі змінні разом з описом внутрішньої функції. В подальшому, після повернення зовнішньої функції, виклику внутрішньої функції локальні змінні зовнішньої функції будуть їй як і раніше доступні.


Тут JavaScript збереже значення змінної htmlElement після повернення конструктора, щоб воно залишалося доступним анонімним функцій при виклику їх браузером. Це дозволить їм знайти свої HTML-елементи і перейти від покажчиків до об’єктів FadingTooltip без допомоги браузера.


Оскільки замикання функцій є особливістю мови JavaScript, вони прекрасно працюють в браузерах, які використовують модель подій W3C. Їх можна було б використовувати для того, щоб включити значення аргументу конструктора htmlElement в анонімні функції, описані в попередньому розділі, замість того, щоб використовувати вбудовану змінну this.


Перехоплення подій курсора в застарілих версіях браузерів


Для застарілих версій браузерів, які не підтримують моделі подій W3C або Internet Explorer, доведеться виконувати перехоплення подій за допомогою оригінальної моделі подій, запропонованої більш ранніми версіями браузера Netscape Navigator. Вона підтримується всіма популярними браузерами і широко використовується web-дизайнерами для анімації web-сторінок, але це самий небажаний варіант для розробки більш складних додатків, оскільки ця модель не може виконувати зчеплення кількох обробників подій. Щоб виконати сам перехоплення, включите покажчик на попередньо зареєстровані функції події в опис ваших власних функцій подій, а потім викличте їх після виклику методу handleEvent, Як показано в лістингу 5.


Лістинг 5. Код JavaScript для перехоплення подій курсора в старих браузерах





function FadingTooltip(htmlElement, tooltipContent, parameters) {

else { // for older browsers
var self = this;
var previousOnmouseover = htmlElement.onmouseover;
htmlElement.onmouseover = function(event) {
self.handleEvent(event ? event : window.event);
if (previousOnmouseover) {
htmlElement.previousHandler = previousOnmouseover;
htmlElement.previousHandler(event ? event : window.event);
}
};
… and similarly for “onmousemove” and “onmouseout” …
}
}

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


Для різноманітності, цей код копіює змінну конструктора this, Що вказує на об’єкт FadingTooltip, в локальну змінну з ім’ям self, А потім використовує покажчик self, щоб знайти об’єкт FadingTooltip в описі анонімної функції. Таким чином, покажчик на об’єкт FadingTooltip включається в опис анонімної функції, щоб його можна було знайти безпосередньо при виклику функцій браузером, незалежно від надання покажчика на HTML-елемент браузером і без необхідності зберігати покажчик на об’єкт FadingTooltip в HTML-елементі.


Можна також включити покажчик на об’єкт FadingTooltip в анонімні функції, які ви описали для моделей подій W3C і Microsoft. Завдяки цьому відпадає необхідність в збереженні покажчиків на наші об’єкти в HTML-елементах, а для знаходження HTML-елемента у всіх моделях події застосовується один і той же метод. Конструктор в даному вихідному коді запрограмований з цього ж принципу.


Тепер, коли ми передбачили перехоплення подій у всіх популярних браузерах, наш конструктор об’єкта готовий, і ми повертаємося до прототипу об’єкта.


Налаштування таймерів і перехоплення подій таймерів


Ми закінчили роботу над конструктором FadingTooltip і можемо продовжити роботу над прототипом. У JavaScript прототип об’єкта може включати методи та змінні; методи – це звичайні змінні, які вказують на функції. Ми почнемо з деяких методів загального призначення, які запускають і скасовують таймери.


Можливо, ви пам’ятаєте з першої частини цієї серії статей, що JavaScript надає два типи таймерів, разові таймери і тікери, причому кінцевому автомату необхідні обидва типи. Ви можете запустити таймер, викликавши функцію setTimeout або setInterval, Передавши значення часу (у мілісекундах), при цьому функція буде викликана, відповідно, після закінчення часу таймера або настання події timetick. Ці функції повернуть глухі посилання, які потім можна передати функції clearTimeout або clearInterval для скасування таймера.


Браузери викличуть функції подій таймера, які були передані як аргументи функції setTimeout і setInterval , Відразу після закінчення значення timeout, Або багаторазово при кожному настанні інтервалу timetick, Відповідно, поки ці таймери не будуть скасовані. Але ці функції timeout і timetick не отримують методів від будь-яких об’єктів. Коли вони викликаються браузером, мінлива this вказує на глобальний об’єкт window. Браузер також не передає цих функцій ніякої інформації про події таймера.


Після нашої боротьби з подіями курсора, перехоплення подій таймерів вже не представляє складності. При завданні таймера скопіюйте вбудовану змінну this, Що вказує на об’єкт FadingTooltip, що містить змінні станів, в локальну змінну з ім’ям self в лексичному контексті викликів функцій setTimeout і setInterval. Задайте опису для анонімних функцій, які використовують змінну self, і передайте ці функції в якості аргументу функцій setTimeout і setInterval. Завдяки цьому змінна self буде включена в опис функції, тому вона залишиться доступною при виклику функцій браузером, як показано в лістингу 6.


Лістинг 6. Код JavaScript для завдання таймера і перехоплення подій таймера





FadingTooltip.prototype = {

startTimer: function(timeout) {
var self = this;
this.currentTimer =
setTimeout( function() { self.handleEvent( { type: “timeout” } ); },
timeout);
},
startTicker: function(interval) {
var self = this;
this.currentTicker =
setInterval( function() { self.handleEvent( { type: “timetick” } ); },
interval);
},


Отримані функції подій таймера роблять не більше, ніж функції для подій курсора. Вони створюють тривіальний об’єкт події таймера, який містить тільки один тип подій – або timeout , Або timetick – І передає його того ж методу handleEvent, Який виконує обробку подій курсора.


Створення таблиці дій / переходів


У JavaScript прототипи об’єктів можуть містити такі структури даних, як масиви та інші об’єкти, а також змінні і методи. Елементи звичайних масивів індексуються цілими числами, тоді як елементи асоціативних масивів індексуються не по числах, а по іменах. У JavaScript асоціативні масиви та об’єкти мають зовсім різний синтаксис для доступу до одних і тих же даних: до властивостей об’єкта можна звертатися як до елементів асоціативного масиву, як показано в лістингу 7.


Лістинг 7. Код JavaScript для доступу до властивостей об’єкта як до елементів асоціативного масиву





if ( htmlElement.fadingTooltip == htmlElement[“fadingTooltip”] ) … // always true

Ви зможете скористатися перевагами цього підходу, якщо реалізуєте свою таблицю станів як двовимірний асоціативний масив функцій. Використовуйте імена станів і подій безпосередньо в якості індексів масиву. Непусті осередки масиву вкажуть на анонімні функції, які виконають дії для подій за допомогою виклику допоміжних методів (таких як запуск і скасування таймерів), А потім повернуть наступний стан. Ядро методу handleEvent викличе ці функції дії / переходу, використовуючи синтаксис масиву, приблизно так, як показано в лістингу 8.


Лістинг 8. Код JavaScript для виклику анонімних функцій, що зберігаються в асоціативному масиві





var nextState = this.actionTransitionFunctions[this.currentState][event.type](event);

Метод handleEvent звернеться до таблиці actionTransitionFunctions як до асоціативного масиву, використовуючи поточний стан і тип події в якості індексів, і вибере спричинюється функцію. Він передасть об’єкт події цієї функції в якості аргументу. Функція виконає необхідні дії, а потім поверне ім’я наступного стану.


Оскільки асоціативні масиви є об’єктами (і навпаки), можна визначити таблицю actionTransitionFunctions, Використовуючи синтаксис об’єкта, навіть якщо метод handleEvent звернеться до неї, використовуючи синтаксис масиву. Наприклад, в початковому стані Inactive єдиним очікуваною подією буде mouseover, Тому можна задати опис для функції для обробки цієї ситуації, як показано в лістингу 9.


Лістинг 9. Код JavaScript для збереження анонімної функції в якості властивості об’єкта





FadingTooltip.prototype = {

initialState: “Inactive”,
actionTransitionFunctions: {
Inactive: {
mouseover: function(event) {
this.cancelTimer();
this.saveCursorPosition(event);
this.startTimer(this.pauseTime*1000);
return “Pause”;
}
},


Прототип для об’єкта FadingTooltip object включає властивість actionTransitionFunctions, Значення якого – інший об’єкт. Він включає властивість з ім’ям Inactive, Значення якого ще один об’єкт. Він містить одну властивість, з ім’ям mouseover, Значення якого – функція. Якщо подія mouseover настане в стані Inactive, Метод handleEvent викличе цю функцію. Функція буде очікувати аргументу з ім’ям event, Виконає три дії, викликавши три допоміжних функції, а потім поверне Pause як ім’я наступного стану. Як одну з дій, буде виконано збереження позиції курсора, яку браузери зберігають в об’єктах подій миші, і запуск таймера, значення таймауту якого – це параметр з ім’ям pauseTime (Заданий в секундах, які будуть переведені в мілісекунди, як вимагає метод startTimer).


Нашому віджету доведеться реагувати на три різних події в стані Pause: mousemove, mouseout і timeout. Задайте визначення для об’єкта Pause в таблиці actionTransitionFunctions, Яка містить властивості для кожного з цих типів подій, як показано на малюнку 10.


Лістинг 10. Код JavaScript для функції, яка реагує на події курсора в стані Pause





FadingTooltip.prototype = {

actionTransitionFunctions: {

Pause: {
mousemove: function(event) {
return this.doActionTransition(“Inactive”, “mouseover”, event);
},
mouseout: function(event) {
this.cancelTimer();
return “Inactive”;
},
timeout: function(event) {
this.cancelTimer();
this.createTooltip();
this.startTicker(1000/this.fadeRate);
return “FadeIn”;
}
},


Коли в стані Pause відбудеться подія mousemove, Метод handleEvent викличе функцію, яка просто викличе метод doActionTransition, Передавши свій аргумент event і повернувши результат, який поверне цей метод. Метод doActionTransition, як ви могли здогадатися, звертається до таблиці actionTransitionFunctions, використовуючи свої перші два аргументи як індекси масиву, аналогічно методу handleEvent , І передає свій третій аргумент функції, яку він виявить в цьому масиві. Коли відбудеться подія mouseout, Наш код викличе функцію, яка скасує таймер, запущений раніше в даному розділі, а потім перейде назад в стан Inactive.


Або, якщо відбудеться подія timeout, Ви скасуєте всі таймери, які можуть виконуватися, створите підказку з вихідною непрозорістю, рівної нулю, запустіть тікер і виконайте перехід в стан FadeIn.


В якості ще одного прикладу функції з таблиці actionTransitionFunctions, Задайте опис функції для обробки подій timetick в стані FadeIn як показано в лістингу 11.


Лістинг 11. Код JavaScript для функції, яка реагує на події таймера в стані FadeIn





FadingTooltip.prototype = {

actionTransitionFunctions: {

FadeIn: {

timetick: function(event) {
this.fadeTooltip(+this.tooltipOpacity/(this.fadeinTime*this.fadeRate));
if (this.currentOpacity>=this.tooltipOpacity) {
this.cancelTicker();
this.startTimer(this.displayTime*1000);
return “Display”;
}
return this.CurrentState;
}
},
….

При кожному настанні події timetick в стані FadeIn метод handleEvent викличе функцію, яка трохи збільшить непрозорість підказки. Параметри функції: тривалість поступового посилення видимості (в секундах), параметри анімації наростання непрозорості, починаючи від нуля (у кроках в секунду), і максимальна непрозорість (задана у вигляді плаваючої величини між 0.0 і 1.0). Функція поверне поточний стан, залишивши кінцевий автомат в стані FadeIn до тих пір, поки параметр непрозорості підказки не досягне максимального значення. Потім вона скасує тікер, запустить таймер на відображення підказки і виконає перехід в стан Display.


Частина функцій в таблиці actionTransitionFunctions описується аналогічним способом. Подробиці дивіться в повному вихідному коді, Який забезпечений великою кількістю коментарів; порівняйте цей вихідний код з малюнком 1.


Реалізація обробника подій


Ми так часто і так переконливо посилалися на метод handleEvent, що може здатися, що його реалізація не обіцяє ніяких складнощів, як показано в лістингу 12.


Лістинг 12. Код JavaScript для обробника подій





FadingTooltip.prototype = {

handleEvent: function(event) {
var actionTransitionFunction =
this.actionTransitionFunctions[this.currentState][event.type];
if (!actionTransitionFunction)
actionTransitionFunction = this.unexpectedEvent;
var nextState = actionTransitionFunction.call(this, event);
if (!this.actionTransitionFunctions[nextState])
nextState = this.undefinedState(nextState);
this.currentState = nextState;
},


Насправді реалізація доступу до таблиці actionTransitionFunctions відрізняється від коду, рекомендованого в попередньому розділі. Цей метод вибирає спричинюється функцію з таблиці actionTransitionFunctions , Використовуючи поточний стан і тип події в якості індексів асоціативного масиву. Однак метод копіює покажчик в локальну змінну вибраної функції, а потім викликає функцію за допомогою методу call об’єкта функції, замість того, щоб викликати її безпосередньо. Це можна зробити тому, що об’єкти функцій можуть бути присвоєні змінним, так само, як будь-яке інше значення. Це потрібно зробити тому, що вбудована мінлива this повинна вказувати на об’єкт FadingTooltip під час виконання функції. Якби ви дійсно викликали функцію безпосередньо з таблиці actionTransitionFunctions, Використовуючи індекси масиву, як було описано раніше, то змінна this вказала б на таблицю. Метод call функції присвоює змінної this свій перший аргумент, а потім викликає функцію, передаючи решту аргументу.


Не забувайте, що таблиця actionTransitionFunctions є розрідженій; ми ввели опису функцій для подій, які очікуються в кожному стані, а всі інші осередки залишили порожніми. Метод handleEvent для обробки будь-якого непередбаченого події викличе метод unexpectedEvent. Або, якщо функція дію / перехід поверне деяке значення, яке не є коректним станом, вона викличе метод undefinedState. Ці методи скасують всі запущені таймери, видалять всі підказки, якщо такі були створені, і повернуть кінцевий автомат в початковий стан. Один з таких методів показаний в лістингу 13; інші майже ідентичні йому.


Лістинг 13. Код JavaScript для обробника непередбаченого події





FadingTooltip.prototype = {

unexpectedEvent: function(event) {
this.cancelTimer();
this.cancelTicker();
this.deleteTooltip();
alert(“FadingTooltip received unexpected event ” + event.type +
” in state ” + this.currentState);
return this.initialState;
},


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


І нарешті, відображення підказки


Оскільки для реалізації не залишилося нічого, крім самої підказки, відкладати більше немає сенсу.


Ми хочемо, щоб підказка з’являлася поряд з курсором, коли подія timeout відбувається в стані Pause, Але браузери не передають інформацію про позицію курсора подій таймера. На щастя, браузери передають інформацію про позицію курсора подіям курсора, тому потрібно зберегти ці дані в змінних стану, викликавши метод saveCursorPosition при настанні події курсора, як показано в лістингу 14.


Лістинг 14. Код JavaScript для збереження позиції курсора





FadingTooltip.prototype = {

saveCursorPosition: function(event) {
this.lastCursorX = event.clientX;
this.lastCursorY = event.clientY;
},


Підказка являє собою HTML-елемент Division, що містить певний текст, зображення і розмітку, які передаються в конструктор в аргументі tooltipContent. Метод createTooltip показаний в лістингу 15.


Лістинг 15. Код JavaScript для створення підказки





FadingTooltip.prototype = {

createTooltip: function() {
this.tooltipDivision = document.createElement(“div”);
this.tooltipDivision.innerHTML = this.tooltipContent;

if (this.tooltipClass) {
this.tooltipDivision.className = this.tooltipClass;
} else {
this.tooltipDivision.style.minWidth = “25px”;
this.tooltipDivision.style.maxWidth = “350px”;
this.tooltipDivision.style.height = “auto”;
this.tooltipDivision.style.border = “thin solid black”;
this.tooltipDivision.style.padding = “5px”;
this.tooltipDivision.style.backgroundColor = “yellow”;
}

this.tooltipDivision.style.position = “absolute”;
this.tooltipDivision.style.zIndex = 101;
this.tooltipDivision.style.left = this.lastCursorX + this.tooltipOffsetX;
this.tooltipDivision.style.top = this.lastCursorY + this.tooltipOffsetY;

this.currentOpacity = this.tooltipDivision.style.opacity = 0;

document.body.appendChild(this.tooltipDivision);
},


Якщо ім’я класу CSS визначено як параметр, застосуйте його до відображення вашого HTML-елемента division. В іншому випадку застосуйте будь базове стильове оформлення за замовчуванням. Але деякі аспекти поведінки підказки залежать від параметрів її відображення, наприклад, позиції і непрозорості, тому все, що має відношення до цих властивостях і може бути визначено в таблиці стилів, потрібно скасувати. HTML-елемент division позиціонується на сторінці в аболютно координатах, починаючи від точки, розташованої поруч із збереженою позицією курсору, вище всіх інших розташованих пошарово елементів. Початковий значення непрозорості дорівнює нулю, що відповідає повній прозорості.


При кожному настанні події timetick в стані FadeIn або FadeOut буде викликаний метод fadeTooltip, Який до певної міри збільшить або зменшить непрозорість підказки, забезпечуючи в той же час, щоб вона залишалася в діапазоні від нуля до максимального значення параметра, як показано в лістингу 16.


Лістинг 16. Код JavaScript для поступового появи / зникнення підказки





FadingTooltip.prototype = {

fadeTooltip: function(opacityDelta) {
this.currentOpacity += opacityDelta;
if (this.currentOpacity<0)
this.currentOpacity = 0;
if (this.currentOpacity>this.tooltipOpacity)
this.currentOpacity = this.tooltipOpacity;
this.tooltipDivision.style.opacity = this.currentOpacity;
},


Функції дію / перехід також потребують допоміжних методах для переміщення і видалення підказки. Реалізація цих методів не представляє складності і повністю описується в примітках до вихідного коду.


Як уже зазначалося у цій статті, перш ніж реалізацію можна буде вважати закінченою, необхідно задати параметри. Вони являють собою властивості прототипу об’єкта, але, на відміну від змінних стану, вони мають значення за замовчуванням, як показано в лістингу 17.


Лістинг 17. Код JavaScript для завдання параметрів прототипу об’єкта





FadingTooltip.prototype = {
… tooltipClass: null, / / ​​ім’я стилю CSS, який слід застосувати до підказкою / / “Null” для стилю по замовчуванню tooltipOpacity: 0.8, / / ​​максимальна непрозорість підказки, / / Від 0.0 до 1.0 включно / / (Після посилення, до початку ослаблення) tooltipOffsetX: 10, / / ​​горизонтальне зміщення курсору від лівого верхнього / / Кута підказки tooltipOffsetY: 10, / / ​​вертикальне зміщення курсору від лівого верхнього / / Кута підказки fadeRate: 24, / / ​​частота фаз анімації для посилення й ослаблення / / Кроків в секунду pauseTime: 0.5, / / ​​на який час курсор повинен затриматися над HTML-елементом, / / Щоб почалося поява (посилення непрозорості) / / Підказки, в секундах displayTime: 10, / / ​​скільки часу відображати підказку (після появи / / До початку зникнення (ослаблення непрозорості), в секундах fadeinTime: 1, / / ​​час анімації появи підказки, в секундах fadeoutTime: 3, / / ​​час анімації зникнення підказки, в секундах

};

Додатковий аргумент parameters конструктора об’єкта являє собою об’єкт, закодований в нотації JavaScript Object Notation (яку іноді називають JSON), який може скасувати значення за замовчуванням для будь-якого з цих властивостей, як показано в лістингу 18.


Лістинг 18. Код JavaScript для ініціалізації параметрів в конструкторі об’єкта





function FadingTooltip(htmlElement, tooltipContent, parameters) {

for (parameter in parameters) {
if (typeof(this[parameter])!=”undefined”)
this[parameter] = parameters[parameter];
}

};

Конструктор перевіряє кожне властивість у своєму аргументі parameters; Для кожного з них, якщо властивість існує в прототипі, його значення скасовує значення параметра за замовчуванням. Не забувайте, що прототип – це об’єкт, крім того, він також є асоціативним масивом. Тут ми знову використовуємо нотацію об’єкта, щоб визначити параметри, і нотацію масиву, щоб звернутися до нього.


На цьому реалізація віджету FadingTooltip закінчена. Ви можете завантажити реальний вихідний код для конструктора і прототипу.


Кілька слів про продуктивність


Перш, ніж переходити до тестування реалізації, обов’язково треба сказати кілька слів про продуктивність.


Браузери виконують програми JavaScript синхронно. Якщо відбувається перехоплення події, браузери викликають обробник події і чекають його повернення, перш ніж перейти до наступної події. Якщо до повернення обробника події відбудеться ще декілька подій, браузер поставить їх у чергу на виконання, поки не буде повернений обробник події, а потім обробить події в черзі по порядку, по одному за один раз, синхронно. Якщо обробник події змушує чекати занадто довго, він може затримати власну реакцію браузера на події, які не перехоплюються кодом JavaScript. Користувачі можуть подумати, що програма занадто повільна або браузер працює некоректно.


У будь-якому випадку важливо зберігати цикл обробника події як можна більш коротким, але це особливо важливо в програмах, подібних щойно описаної, що імітує анімацію з подіями таймера, що відбуваються з короткими часовими проміжками. Якщо обробник події timetick вимагає більше часу, ніж інтервал тікера, то події timetick будуть нагромаджуватися одне на інше в черзі подій браузера, навантажуючи процесор і викликаючи відмови в реагуванні браузера.


Наприклад, якщо значення параметра анімації за замовчуванням дорівнює 24 кроків в секунду, то у обробника події timetick є щонайбільше 40 мілісекунд на те, щоб встигнути зробити все, що необхідно, до повернення в браузер, за умови, що у нього в розпорядженні всі ресурси процесора. На сучасних робочих станціях за цей час можна зробити досить багато. Тим не менш, ваша задача полягає не в тому, щоб зробити за доступний період часу якомога більше, а в тому, щоб використовувати якомога менше процесорного часу. Якщо використання процесора вашої реалізацією програми незначно, то анімація буде виконуватися плавно, а наша програма буде реагувати на запити навіть в тих випадках, коли процесор буде перевантажений іншими завданнями.


Не піддавайтеся спокусі збільшити частоту кроків анімації до 60 або 85 в секунду, думаючи, що якщо вона буде відповідати частоті відновлення вашого монітора, то анімація вийде більш плавною. При цьому часовий проміжок між подіями timetick зменшиться приблизно до 12 мілісекунд. Якщо ваш обробник події timetick буде тривати довше цього часу, або відбудеться конфлікт використання ресурсів процесора, то анімація може демонструватися скачками, а браузер перестане відповідати на запити.


Готовність до тестування


Закінчивши реалізацію програми, ви підготувалися до того, щоб протестувати її на деяких реальних браузерах. Про це піде мова в третій частині даної серії статей. Однак не забувайте, що розробка – Це ітеративний процес, можливо, доведеться повернутися до фази проектування або реалізації …

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*