Опубликован: 10.12.2007 | Доступ: свободный | Студентов: 822 / 20 | Оценка: 5.00 / 5.00 | Длительность: 58:33:00
Лекция 6:

События

6.1.2. Модель событий DOM

Обработчики событий и сами события, которые ежедневно используются web-разработчиками, появились в стандартах W3C DOM. Многие из таких событий относятся к любым XML-документам, в том числе написанным на XUL, и опыт работы с этими обработчиками можно напрямую использовать в Mozilla. Система событий DOM находится выше основной очереди событий Mozilla. События DOM ограничены одним XML-документом.

Так как совсем недавно появился законченный стандарт DOM 3 Events, поддержку событий в web-браузерах нужно немного доработать. Вопросы совместимости с разными браузерами, нестандартное поведение событий и отсутствие некоторой части функциональности усложняют использование событий в скриптах. В целом, "простая" модель событий третьей версии браузеров, в которой события распространяются не очень далеко - все, чем разработчики рискуют пользоваться при создании кроссплатформенного HTML.

С выходом Mozilla многие из этих проблем исчезли. Для разработчиков приложений и XUL-документов нет никаких затруднений с совместимостью. Mozilla полностью поддерживает стандарт DOM 2 Events и работу со многими распространенными событиями из DOM 3 Events. Поддерживаются и фаза перехвата события, и фаза его возврата.

Mozilla также поддерживает использование нескольких обработчиков для одного события, как предусмотрено стандартами. Настоятельно рекомендуется прочитать разделы 1.1 и 1.2 стандарта DOM 3 Events, если вы еще этого не сделали. Если кратко описать содержимое этих разделов, то события в XML-документе могут обрабатываться в скриптах JavaScript. Часть скрипта или одна функция будет обработчиком события, который программист настраивает на целевой объект события. Целевой объект события - просто тег или DOM-объект Element тега плюс тип события. При совершении события в результате пользовательского ввода или по какой-нибудь другой причине создается объект Event. Этот объект переходит вниз по DOM-иерархии тегов, начиная с объекта Document и заканчивая целевым тегом события. Это фаза перехвата, и любой тег с подходящим обработчиком в течение всего пути может обработать путешествующее событие. Такой вклинивающийся обработчик может остановить событие или позволить ему перемещаться дальше к целевому тегу.

Когда объект Event достигает целевого тега, выполняется назначенный для этого события обработчик. Этот обработчик может остановить движение объекта Event и предотвратить выполнение действия по умолчанию. Действие по умолчанию - то, что случилось бы, если бы у целевого тега не было обработчика вообще. После выполнения обработчика запускается действие по умолчанию (если событие не было остановлено), и событие переходит в фазу возврата. В этой фазе объект Event поднимается по DOM-иерархии обратно к объекту Document, опять же, по пути он может быть перехвачен одним или несколькими обработчиками. По достижении объекта Document событие завершается.

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

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

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

В таблице 6.1 приведены события, распознаваемые Mozilla. Этот список получен из исходного файла Mozilla content/events/src/nsDOMEvent.cpp и организован в соответствии с разделами стандарта DOM 2 Events, плюс две колонки отведены под собственные события Mozilla. Многие XUL-теги поддерживают только подмножество перечисленных событий.

Таблица 6.1. События, реализованные в Mozilla
DOM: мышь DOM: текст DOM: HTML DOM: преобразование Mozilla Mozilla
click keydown load DOMNodeInserted dblclick popupshowing
mousedown keyup unload DOMNodeRemoved contextmenu popupshown
mouseup abort DOMAttrModified popuphiding
mouseover error DOMCharacterDataModified keypress popuphidden
mousemove select text
mouseout change command dragenter
submit commandupdate dragover
reset input dragexit
focus dragdrop
blur paint draggesture
resize overflow
scroll underflow broadcast
overflowchanged close

Собственные события Mozilla обсуждаются в соответствующем разделе лекции. Здесь приведено лишь краткое описание этих событий. Событие dblclick возникает при двойном щелчке по кнопке мыши. contextmenu происходит, если ввод с клавиатуры или мыши вызвал контекстное меню (например, при нажатии правой кнопки мыши в Microsoft Windows). События, сочетающиеся с keypress, соответствуют вводу с клавиатуры или действиям, которые обычно вызываются пользователем. События, сочетающиеся с paint, связаны с перерисовкой документа. События popup... возникают при появлении всплывающих меню и подсказок. События drag... используются при перетаскивании объектов мышью. broadcast поддерживает систему "источник событий-наблюдатель", специфичную для Mozilla. Событие close происходит, когда пользователь пытается закрыть окно.

Все эти события создают в соответствии со стандартом DOM Events объект Event. Интерфейс nsIDOMEvent в Mozilla эквивалентен интерфейсу Event стандарта, имена некоторых других интерфейсов преобразуются похожим образом.

В таблице 6.2 показаны события, которые в версии 1.4 Mozilla еще не обрабатывает. Эти события также взяты из стандарта DOM 3 Events.

Таблица 6.2. Стандартные события, еще не реализованные в Mozilla 1.2.1
DOM: события пользовательского интерфейса DOM: события изменения имени DOM: события текста
DOMFocusIn DOMElementNameChanged TextInput (используйте keypress или text)
DOMFocusOu DOMAttributeNameChanged
DOMActivate DOMSubtreeModified
DOMNodeInsertedIntoDocument
DOMNodeRemovedFromDocument

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

<!-- XML-вариант --> 
<button id="test" onclick="myhandler(event);"> 
  <label value="Нажми меня"/> 
</button>
// DOM-вариант 
var obj = getElementById("test"); 
obj.addEventListener("click", myhandler, false);
Листинг 6.5. Способы регистрации обработчиков событий

В обычном HTML функции обработчика будет передаваться аргумент this. В Mozilla текущий объект Event всегда доступен через определенное свойство. Имя этого свойства - currentTarget, и это свойство эквивалентно this, так что можно пользоваться и тем, и другим.

При выполнении кода с использованием разных подходов - XML или DOM - есть небольшие различия. Вариант с использованием DOM чуть более гибкий по нескольким причинам:

  • одному и тому же объекту можно добавлять несколько обработчиков с помощью addEventListener() ;
  • если третий аргумент addEventListener() - true, обработчик может быть запущен в фазе перехвата, что невозможно сделать через XML;
  • обработчики можно удалять с помощью функции removeEventListener(). Если вы задали обработчик в XUL, то вы сможете его там только заменить, но не удалить;
  • при использовании DOM обработчики событий могут быть вынесены за пределы XML-содержимого. Поэтому их можно помещать в отдельный .js-файл.

Но и у XML-подхода есть одно преимущество: можно использовать фрагмент скрипта меньший, чем целая функция. Эта возможность может быть полезной для HTML, но в случае XUL, где рекомендуется структурированное программирование, пользы в этом не так уж много. Если XML-файлу предстоит долгая жизнь, обработчики событий в атрибутах указывать не рекомендуется.

6.1.2.1. Поддержка обработчиков событий в XUL

В соответствии с таблицей 5.4 Mozilla не поддерживает интерфейсы HTMLEvents и TextEvents, являющиеся частью стандарта DOM 3 Events. Но это еще не вся история.

XUL не HTML, так что отсутствие поддержки HTMLEvents неудивительно. Действия по умолчанию для некоторых HTML-тегов важны (например, отправка форм или переход по ссылкам), но действия по умолчанию для XUL-тегов встречаются не так часто. С другой стороны XUL-теги нередко сопровождаются XBL-связями и сложными стилями. Эти связи и стили добавляют эффекты и обработчики, которые также можно посчитать за действия по умолчанию. Если у XUL-тега нет предоставленных программистом обработчиков событий, но при возникновении события все равно что-то происходит, лучше всего начать поиск обработчика в файле xul.css архива toolkit.jar в chrome. Здесь находятся стили по умолчанию и связи для тегов. В "XBL-связки" , "XBL-связки", описывается, как добавлять поддержку событий через XBL-связи.

TextEvents - стандартное для DOM обозначение событий, в которых XML-документу отправляется символ. Проще говоря, это то же самое, что и keypress, но теоретически TextEvents может создаваться и другим источником, а не только клавиатурой. Раздел стандарта, описывающий это событие, основывается на интерфейсе nsIKeyEvents Mozilla, но в некоторых аспектах отличается от него. На практике Mozilla обеспечивает почти ту же функциональность, что описана в стандарте.

У события TextEvents в Mozilla есть один недостаток: его могут получать только объекты Document или Window. Фазы перехвата и возврата отсутствуют.

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

У многих XUL-тегов есть определенные для них XBL-связи, а в XBL есть тег <handler>. Это значит, что действие по умолчанию для данного тега можно задать в XBL, а не в C/C++ где-то внутри Mozilla. Это открывает возможность исследования действий по умолчанию для некоторых XUL-тегов.

6.1.3. События таймера

Вторая система событий внутри Mozilla - простой механизм для создания событий, отложенных на определенное время. В листинге 6.4 и его обсуждении рассматривается именно эта система. Так как очередь событий в Mozilla - фундаментальная особенность платформы, создать удобную для программистов возможность пользоваться таймером и легко, и благоразумно. Четыре вызова, составляющие эту маленькую систему, приведены в листинге 6.6.

timer = window.setTimeout(code, milliseconds); 
timer = window.setInterval(code, milliseconds);
window.clearTimeout(timer); 
window.clearInterval(timer);
// примеры использования таймера timer = 
 setTimeout(myfunction, 100, arg1, arg2); 
timer = setTimeout("myfunction();", 100); 
timer = setTimeout("window.title='Вперед'; go();", 100);
Листинг 6.6. Примеры использования программного интерфейса для событий таймера

Аргумент с именем code может быть или объектом функции, или строкой, содержащей код на JavaScript. Если это строка, она выполняется с помощью eval(). Возвращаемое значение, присваиваемое timer - числовой идентификатор элемента в очереди событий. Его единственная цель - дать возможность удалить из очереди этот элемент. В JavaScript данный идентификатор должен быть лишь уникальным и более ничего.

На таймеры накладываются некоторые ограничения: события таймера не обладают цепочкой областей видимости того кода, который их вызвал; первый член цепочки областей видимости - объект окна; указываемая задержка должна быть не менее 10 миллисекунд.

Но, пожалуй, самое большое ограничение - однопоточная природа и JavaScript, и XPCOM. Никакое событие таймера не произойдет, пока не завершится выполнение текущего скрипта. В любой момент времени может выполняться только одно событие таймера. Если для завершения выполнения данного куска кода требуется много времени, тогда все события, которые также должны быть выполнены, будут отложены и выполнены позднее. Наконец, до начала выполнения некоторых операций ввода-вывода нужно дождаться завершения работы обработчика событий. Например, изменения стиля документа, вызываемые с помощью события setTimeout(), не произойдут, пока это событие не завершится. Это основное ограничение для использования анимации. Вообще интерпретатор JavaScript должен освобождать процессор, прежде чем платформа Mozilla сможет продолжать работу.

Система XPCOM в Mozilla также предоставляет доступ к событиям таймера. Здесь есть программный интерфейс, полностью независимый от setTimeout(). Нужная в этом случае пара компонента и интерфейса:

@mozilla.org/timer;1 nsITimer

Такой объект работает со вторым объектом, поддерживающим интерфейс nsIObserves. Чтобы использовать nsITimer, потребуется реализовать этот второй объект самостоятельно. В листинге 6.7 приведен пример.

var observer = { // Components.interfaces.nsIObserver observe: 
 function(aSubject, aTopic, aData) {
  alert("From: " + aSubject + " saw: " + 
    aTopic + " data: " + aData); 
}, // Components.interfaces.nsISupports QueryInterface : 
function(iid) {
  if ( iid.equals(Components.interfaces.nsIObserver) || 
     iid.equals(Components.interfaces.nsISupportsWeakReference) 	
  || iid.equals(Components.interfaces.nsISupports) 	)
  return this; 
  throw Components.results.NS_NOINTERFACE; 
  } 
}; 
with(netscape.security.PrivilegeManager) { 
  enablePrivilege("UniversalXPConnect"); 
}
var c, timer; 
c = Components.classes["@mozilla.org/timer;1"]; 
timer = c.createInstance(Components.interfaces.nsITimer); 
 timer.init(observer, 5000, timer.TYPE_ONE_SHOT);
Листинг 6.7. Вызов setTimeout(), реализованный с помощью XPCOM-компонентов

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

Последние строки листинга 6.7 создают объект Timer и определяют событие. init() сообщает таймеру, что объект должен использоваться, когда настанет срок создания события, а также должно ли событие вызываться один раз или многократно. Как и в случае setTimeout(), затем этот скрипт завершает работу. Событие происходит позже, вызывая запуск observer.observe(). Понятно, что этот скрипт делает то же самое, что и setTimeout(), вызывающий alert().

Строка кода в середине этого примера, взаимодействующая с системой защиты, необходима в том случае, если скрипт хранится вне chrome. Из соображений безопасности пользователь должен подтвердить, что скрипту дается разрешение на использование XPCOM. Здесь это необходимо только для того, чтобы продемонстрировать, что необходимо для XUL, не хранящегося в chrome. Большая часть XUL-приложений хранится в chrome, и им эта строка не нужна.

Если включить этот код в любой HTML- или XUL-документ, примерно через пять секунд после его загрузки появится окно с сообщением. В обычных случаях гораздо легче пользоваться функциями setTimeout() и setInterval(). Позже эта формальная система покажется в некоторых случаях более удобной.