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

События

6.1.4. Источники событий и наблюдатели

Третья система событий в Mozilla основывается на шаблоне проектирования Observer ("наблюдатель"). Напомним, что шаблоны проектирования - это идеи проектирования, которые, если реализовать их аккуратно, создают мощные, гибкие и иногда простые программные системы. В XPCOM-компонентах Mozilla, которые вместе похожи на библиотеку объектов, упомянутый выше шаблон проектирования применяется широко. Любой случай нетривиального использования XPCOM подразумевает понимание этого шаблона. Более того, эта техника используется так интенсивно, что проявляется прямо в XUL-тегах.

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

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

Наблюдатели работают с источниками событий. Задача источника события - уведомить наблюдателя о том, что произошло какое-то событие. Источник событий имеет возможность оповещать любое число наблюдателей, даже если произошло только одно событие. Отношение между источником событий и его наблюдателями есть отношение "один ко многим". Как одно событие DOM 2 может обрабатываться несколькими обработчиками при одном целевом объекте, так и несколько наблюдателей могут обрабатывать одно событие.

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

Все это было несколько абстрактно, перейдем теперь к конкретной технологии.

6.1.4.1. Источники событий и наблюдатели в XUL

XUL поддерживает теги <broadcasterset>, <broadcaster> и <observes>. Есть также атрибуты observes и onbroadcast, которые применимы ко всем XUL-тегам. С ними также связан тег <command>, который рассматривается в "Команды" , "Команды".

Эти теги позволяют отображать изменения значений XML-атрибутов одного тега в другой. Другими словами, два тега связываются атрибутом. Эти изменения используются в XUL как система уведомлений. Синтаксис для этих тегов показан в листинге 6.8.

<broadcasterset> 
  <broadcaster id="producer1" att1="A" att2="B" ... /> 
  <broadcaster id="producer2" att1="C" ... /> 
</broadcasterset> 
<observes element="producer1" 
  attribute="att1" 
  onbroadcast="alert('тест1')"/> 
<observes element="producer1" 
  attribute="att1 att2" 
  onbroadcast="alert('тест2')"/> 
<box observes="producer2"/>
Листинг 6.8. Источники событий и наблюдатели в XUL

Вместо att1 и att2 могут быть любые имена XML-атрибутов, кроме id, ref и persist. Ни у одного из этих тегов нет визуального отображения, кроме, возможно, <box>. В последней строке мог использоваться любой XUL-тег; никаких особых причин для выбора <box> нет. Ниже представлен обзор новых тегов.

Вспомним, что тег <stringbundleset> был введен в "Статическое содержимое" , "Статическое содержимое". Это неотображаемый тег без особого поведения. Он просто используется как удобный контейнер для тегов <stringbundle>. <broadcasterset> играет ту же роль, но для тегов <broadcaster>. Он лишь делает исходный код более удобным для чтения; однако рекомендуется следовать этой договоренности.

У тега <broadcaster> есть уникальный идентификатор и набор атрибутов. Эти атрибуты - обычные текстовые данные и не несут какой- либо особой смысловой нагрузки. У тега может быть столько атрибутов, сколько нужно. При изменении любого из них источник событий (сам этот тег) будет искать, кому можно отправить события.

Тег <observes> использует атрибут element, чтобы подписаться на уведомления от источника событий с таким же идентификатором. У этого тега также должен быть атрибут attribute, определяющий, изменения каких атрибутов источника событий интересны данному наблюдателю. Второй пример в листинге 6.8 демонстрирует тег <observes>, наблюдающий за двумя атрибутами. Значением атрибута также может быть *, что означает наблюдение за всеми атрибутами источника событий.

Атрибут onbroadcast применим только к тегу <observes>. Он необязателен. События, получаемые от источника событий, будут регистрироваться, даже если этот обработчик отсутствует. Если же он есть, он будет запущен перед изменением значения атрибута. В этом случае старое значение атрибута можно будет прочитать из тега, а новое - из события. Обработчик onbroadcaster в некоторых версиях Mozilla не всегда работает корректно. Он может вызываться дважды или не вызываться вообще - и того, и другого быть не должно. Он работает надежно, если значение attribute у наблюдателя - имя одного атрибута источника событий.

Наконец, в этом примере используется атрибут observes, применимый к любому XUL-тегу и позволяющий отслеживать изменения любых атрибутов данного источника событий. Это то, что мы видим в теге <box>. При использовании observes ограничить наблюдение конкретными атрибутами нельзя. Тег с атрибутом observes получает уведомления об изменении любых атрибутов источника событий.

В листинге 6.9 показано, как это все работает, в виде последовательности частей кода.

<button label="Нажми меня" oncommand="produce('bar');"/>
function produce(str) {
  getElementById("b").setAttribute("foo",str); 
} 

function consume1(obj) { 
  if (obj.getAttribute("foo") == "bar") 
    getElementById("x1").setAttribute("style","color:red"); 
}
<broadcaster id="b" foo="default"/>
<observes id="o1" 	element="b" 
  attribute="foo" 
  onbroadcast="consume1(this)"/>

<box id="x1"><label value="что-то">
</box>
Листинг 6.9. Порядок обработки события

В этом примере событие начинается и завершается в XUL-тегах <button> и <box> соответственно. Это обычная техника, но необязательная. Две функции JavaScript, produce() и consume1() - настоящие точки начала и завершения события.

produce() создает событие, меняя значение атрибута foo тега источника событий, который, в свою очередь, уведомляет об этом всех своих наблюдателей. В нашем случае есть только один наблюдатель. Затем меняется значение атрибута foo у наблюдателя. В этом месте событие могло завершиться, тогда какой-либо скрипт должен был бы вернуться и проверить атрибут наблюдателя. Но в нашем примере тег <observes> завершает событие вызовом функции consume1(). В довершение всего она что-то делает, в этом случае меняет стиль тега <box>.

Если были и другие наблюдатели, могли бы существовать и функции consume2() и consume3(). Функция consume1() работает с тегом наблюдателя, а не источника данных. Атрибут foo, с которым она работает, копируется в тег наблюдателя при изменении в теге источника событий - так происходит событие.

В нашем примере обработчик кнопки мог задать нужный стиль напрямую, обойдясь меньшим числом тегов и кода. Даже если бы наблюдателей было несколько, и каждый из них совершал бы какое-то действие, обработчик кнопки все равно мог бы реализовать все эти действия напрямую. Листинг 6.9 более сложен, чем при использовании одного только обработчика кнопки потому, что существующий обработчик кнопки понятия не имеет, какие теги наблюдают за производимыми им изменениями. Это значит, что действие <button> не связано жестко с какими-то определенными тегами. Тег <button> предоставляет службу, которой могут воспользоваться все заинтересованные стороны. Это особенно полезно при работе с оверлеями, которые описываются в "Оверлеи и Chrome" , "Оверлеи и Chrome". Оверлеи создают окружение, в котором даже автор приложения не уверен, какие данные могут сейчас отображаться. В таких случаях использование наблюдателей - идеальное решение: просто оповестите всех о своем событии, и те, кому нужно, узнают об этом.

Есть и другой вариант применения <observes>. Любой XUL-тег может содержать <observes> в качестве части своего содержимого. Родительский тег такого <observes> будет получать события от источника данных, указанные в теге <observes>, который в этом случае сам меняться не будет. Если событие должно только менять атрибуты тега, ситуация с вложенными тегами делает общий случай в листинге 6.9 гораздо проще. Функцию consume1() можно убрать, и если имена атрибутов в коде координируются, то в листинге 6.10 показано, как можно установить атрибут тега <box> без написания скриптов.

<button label="Нажми меня" oncommand="produce('color:red');"/> 

function produce(str) { 
  getElementById("b").setAttribute("style",str); 
}

<broadcaster id="b" style=""/> 

<box id="x1"> 
  <observes id="o1" element="b" attribute="style"/> 
  <label value="что-то"> 
</box>
Листинг 6.10. Порядок обработки события

Этот пример можно еще сократить, используя атрибут observes. Для этого следует удалить тег <observes> и добавить атрибут observes="b" тегу <box>. Теперь этот тег будет получать все изменения источника событий, а не только касающиеся атрибута style, но при этом нужно писать меньше кода.

Наконец, тег <broadcaster> также отправляет событие, когда его XUL-страница загружена впервые. В этом случае связанные с этим источником событий обработчики не запускаются.

Рассмотрев механизм работы <broadcaster> и <observes>, неплохо бы еще и узнать, для чего все это нужно. Ответ таков: автор приложения добавляет дополнительный слой значений (семантический слой) поверх основной системы, чтобы достичь какой-то конечной цели. В Mozilla неофициальные, но удобные соглашения об использовании значений атрибута id для тега источника событий позволяют отражать различные смысловые нагрузки этих целей. Приведем три примера.

  1. id="cmd_mycommand". Техника использования наблюдателей может применяться для отправки команды ( cmd_mycommand ) разным адресатам. Эта команда может быть функцией, связанной с источником событий. При возникновении события все наблюдатели получат и выполнят ее. Рабочий пример использования такого атрибута - закрытие нескольких окон за одну операцию.
  2. id="Panel:CurrentFlags". Описанная в этом разделе техника может также применяться для управления ресурсом или объектом. Данный ресурс может быть или не быть ровно одним XUL-тегом. В простейшем случае тег <broadcaster> фактически описывает этот ресурс, храня множество информативных атрибутов. Когда эти атрибуты у ресурса меняются, источник событий оповещает всех наблюдателей, держа их в курсе дела.
  3. id="data.db.currentRow". Наконец, эта техника может применяться как система репликации данных. id может полностью совпадать с именем реального объекта JavaScript или другого элемента данных. При изменении данных источник событий сообщает об этом всем наблюдателям. Эти наблюдатели далее могут, используя обновленный JavaScript-объект, изменить любые подсистемы, которые от них зависят.

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

Когда мы только ввели понятие наблюдателя, было сказано, что наблюдатели часто взаимодействуют со своими источниками данных. Это не так часто встречается в системах на основе XUL, хотя ничто не может помешать обработчику onbroadcast залезть в соответствующий источник событий. Этот прием чаще используется в XPCOM-системе Mozilla, что мы скоро обсудим.