• Содержание

  • Разработка приложений с помощью Mozilla / автор: Н.Макфарлейн

    6. Глава: События


    Платформа Mozilla создана, прежде всего, для интерактивных приложений. В этой главе объясняются основные особенности Mozilla, которые образуют мостик между человеком и программной системой. Эти особенности разнообразны. Они начинаются с драйверов устройств для обработки отдельных нажатий клавиш и заканчиваются формами, меню и панелями инструментов. За всеми этими возможностями скрывается управляемая событиями система обработки ввода. В этой главе описывается основная обработка ввода.

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

    Традиционные интерфейсы между компьютером и пользователем вроде клавиатуры, мыши и монитора совершенно нестандартны. Эти устройства связываются с операционной системой и графической средой пользователя, а названные системы пытаются вводить собственные уникальные стандарты, чтобы извлечь пользу из работы с этими устройствами. Такие стандарты делают проектирование кроссплатформенных приложений сложной задачей. Даже простейшие стандарты задают нелегкую работу: одна кнопка мыши для Macintosh, две - для Microsoft Windows, три - для UNIX.

    С другой стороны, в большинстве операционных систем для операций вырезания, копирования и вставки используются сочетания Control-X, Control-C и Control-V соответственно, если, конечно, на клавиатуре есть кнопки X, C и V. Так что со стандартами связана еще одна проблема: нужно не только выбрать подходящий, но и для многих требуется обеспечивать совместимость. Каждый стандарт немного тиран.

    Платформа Mozilla пытается и честно соответствовать всем этим ограничениям, и искать пути для ввода собственных правил. Пользователи хотят, чтобы их взаимодействие с Mozilla проходило привычным, очевидным, простым и предсказуемым образом. Большая часть пользовательских действий часто повторяется, и пользователи хотят иметь возможность выполнять эти действия по привычке, не задумываясь.

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

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

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

    6.1. Как Mozilla обрабатывает события

    На рисунке 6.1 показано схематическое представление управляемых событиями систем внутри Mozilla.

    Все эти подходы мы рассмотрим хотя бы кратко; многие - подробно, но команды будут обсуждаться отдельно, в главе 9, "Команды", а обработка данных - одна из тем главе 16, "Объекты XPCOM".

    Обзор обработки событий в Mozilla. (Фаза перехвата, Целевой объект, Фаза всплытия, События DOM, Команды, Поток данных, События данных, События таймера, наблюдатель, подписчик, источник событий, Однонаправленное наблюдение, Двунаправленное наблюдение).


    Рис. 6.1.  Обзор обработки событий в Mozilla. (Фаза перехвата, Целевой объект, Фаза всплытия, События DOM, Команды, Поток данных, События данных, События таймера, наблюдатель, подписчик, источник событий, Однонаправленное наблюдение, Двунаправленное наблюдение).

    6.1.1. Планирование ввода и вывода

    Всем программам нужна какая-либо форма ввода и вывода, и платформа Mozilla - не исключение. Она может получать данные от устройств, управляемых человеком, используя подключение к сети и Internet, от операционной системы, от других приложений и от самой себя. Она может также отправлять данные в любое из перечисленных мест. Все вместе образует сложную для управления систему возможностей. Что если данные появляются сразу из нескольких мест? Решение, реализованное в Mozilla, использует простую систему планирования, управляемую событиями.

    Проблема систем планирования заключается в том, что их прямую поддержку обеспечивает лишь небольшая часть языков программирования, и простые программы эти системы используют довольно редко. Некоторые программисты никогда не сталкиваются с такими системами. Здесь предложено введение в подобные системы для новичков. Более опытные программисты могут обратить внимание, что эта система эквивалентна многопоточной среде и вызову ядра select().

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

    var info; while (true) {             read info;                 print info; }


    Листинг 6.1. Пример первой программы с вводом и выводом

    В соответствии с правилами языков третьего поколения, в этой программе есть один блок инструкций, состоящий из двух инструкций, которые постоянно выполняются одна за другой. Эта программа никогда не завершается; она просто выводит все то, что в нее вводят. Она могла быть более структурированной, как показано в листинге 6.2.

    var info;
    function read_data() {
     read info;
     }
    function print_data() {
      print info;
     }
    function run() {
      while (true) {
        read_data();
        print_data();
      }
    }
    run();


    Листинг 6.2. Пример первой структурированной программы с вводом и выводом

    Хотя вторая программа выполняет только одну инструкцию - run() - очевидно, что это точно такой же пошаговый подход, что и в листинге 6.1. Программы, написанные с использованием систем планирования, работают совсем не так. Та же программа, оформленная как система планирования, могла бы выглядеть так, как в листинге 6.3.

    var info;
    function read_data() {
      if (!info)
        read info;
    }
    function print_data() {
      if (info) {
        print info;
        info = null;
      }
    }
    schedule(read_data, 500);
    schedule(print_data, 1000); 
    run();


    Листинг 6.3. Пример первой программы с вводом и выводом на основе системы планирования

    Функция schedule() в этом примере принимает два аргумента: функцию для вызова и временную задержку в миллисекундах. schedule() говорит о том, что указанная функция должна регулярно запускаться по прохождении заданного промежутка времени. run() дает системе команду искать то, что можно запустить. По прохождении половины секунды (500 миллисекунд) будет вызвана функция read_data(). По прохождении секунды (1000 миллисекунд или 2*500 миллисекунд) будут вызваны обе функции. По прохождении полутора секунд (3*500 секунд) read_data() будет вызвана в третий раз и так далее. Функция run() никогда не завершается.

    Такая схема может показаться очень непривычной, особенно потому, что функции schedule() и run() нигде в программе не определены. Вам придется просто поверить, что они работают так, как описано. Это службы, предоставляемые какой-либо уже существующей системой планирования. Хуже то, что read_data() и print_data() выполняются без какого-либо особого порядка. В действительности read_data() вызывается в два раза чаще (каждые полсекунды, а print_data() - только каждую секунду).

    Чтобы работать вместе, эти две функции используют общие данные (переменную info). read_data() не будет ничего читать, пока последние данные из info не будут использованы функцией print_data(). print_data() не будет ничего печатать, пока read_data() не запишет что-нибудь в info. Эти две функции координируются друг с другом с помощью общей информации о состоянии, даже если во всех остальных отношениях они независимы.

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

    Иногда эта система планирования открыта для программиста. Листинг 6.4 очень похож на листинг 6.3; но это корректный и рабочий скрипт на JavaScript. Включите его в любой HTML- или XML-документ, вводите что- нибудь в регулярно появляющееся окно ввода и наблюдайте за заголовком окна - вы увидите, как работает система планирования. Этот пример работает достаточно медленно, чтобы при желании можно было, щелкнув мышью, закрыть окна.

    function read_data() {
      if (!window.userdata)
        window.userdata = prompt("Введите новый заголовок");
    }
    function print_data() {
      if (window.userdata) {
        window.title = window.userdata;
        window.userdata = null; 
      }
    } 
    window.setInterval(read_data, 5000); 
    window.setInterval(print_data, 100);


    Листинг 6.4. Пример планирования вызова функций с помощью setInterval().

    Хотя эти две функции и вызываются с разной частотой, система работает. print_data() будет часто проверять, есть ли для нее работа; read_data() будет запрашивать данные у пользователя, но не так часто.

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

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

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

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

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

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

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

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

    Из главе 5, "Скрипты", нам известно, что большая часть платформы Mozilla доступна как XPCOM-интерфейсы и компоненты, и очереди событий - не исключение. Два компонента

    @mozilla.org/event-queue;1 @mozilla.org/event-queue-service;1

    и интерфейс

    nsIEventQueue

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

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

    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 чуть более гибкий по нескольким причинам:

    Но и у 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. Здесь находятся стили по умолчанию и связи для тегов. В главе 15, "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(). Позже эта формальная система покажется в некоторых случаях более удобной.

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

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

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

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

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

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

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

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

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

    Эти теги позволяют отображать изменения значений 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> был введен в главе 3, "Статическое содержимое". Это неотображаемый тег без особого поведения. Он просто используется как удобный контейнер для тегов <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> предоставляет службу, которой могут воспользоваться все заинтересованные стороны. Это особенно полезно при работе с оверлеями, которые описываются в главе 12, "Оверлеи и 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, что мы скоро обсудим.

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

    Обработчики событий можно задать с помощью XML-атрибута или с помощью JavaScript-метода, основываясь на DOM-стандартах. Оба подхода продемонстрированы в листинге 6.5. Это верно и для источников данных и наблюдателей Mozilla. Однако сходство действительно, только если используется XUL. В листинге 6.11 сравниваются техники создания пар "источник событий - наблюдатель" с помощью XUL и JavaScript.

    <!-- XML-вариант --> <broadcaster id="bc"/> 
      <box id="x1" observes="bc" onbroadcast="execute(this)"/>
     
    // AOM-вариант <broadcaster id="bc"/> 
      <box id="x1"/>
    var bc = getElementById("bc"); 
    var x1 = getElementById("x1"); 
    addBroadcastListenerFor(bc, x1, "foo"); 
    addEventListener("broadcast", execute, false); 
     
    // также removeBroadcastListenerFor(bc, x1, "foo");


    Листинг 6.11. Связывание источников событий и наблюдателей использованием XUL или JavaScript

    Оба эти примера создают отношение "источник событий-наблюдатель" между двумя тегами. Новый метод addBroadcasterListenerFor() объекта XUL-окна создает связь в случае JavaScript. Обратите внимание, что подписчик onbroadcast также нужно задать.

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

    Как и таймеры, источники событий и наблюдатели можно создавать быстро, в стиле web-разработки, с помощью XUL, или медленнее, но более гибко - с помощью XPCOM-компонентов. В листинге 6.7 продемонстрировано частичное использование XPCOM-компонентов. Вспомним, что объект observer явно следует шаблону наблюдателя и что объект nsITimer - в некотором роде источник событий, хотя и ограничен для данного отложенного события только одним наблюдателем.

    Листинг 6.7 можно изучить дальше. На рисунке 6.2 показан вывод единственного вызова alert() в этом примере.

    Аргументы nsIObserver observe(), предоставленные nsITimer


    Рис. 6.2.  Аргументы nsIObserver observe(), предоставленные nsITimer

    Три аргумента, передаваемые методу observe(), отображены в этом окне. Ранее мы не обращали на них внимания, но теперь их можно рассмотреть.

    Третий аргумент - это данные, связанные с событием. nsITimer не предоставляет никаких данных для этого аргумента.

    Второй аргумент - эквивалент атрибута attribute XUL-тега <observes>. Он указывает, какое событие имеет место. Значение timer-callback - особое значение, связанное с XPCOM-событием nsITimer. Это значение указано в C/C++-коде Mozilla, но это нестандартное событие DOM.

    Первый аргумент - сам объект nsITimer. В окне сообщения он был приведен к JavaScript-типу String. Единственный способ сделать это - вызвать для объекта метод toString(). Так что наблюдатель в данном примере выполняет действие над источником данных, чтобы просто получить строку. Это пример активного взаимодействия объектов и источников данных. На самом деле наблюдатель может обращаться к любому свойству источника данных, а не только к методу toString().

    В Mozilla есть и более общая, чем nsITimer, поддержка систем "источник данных-наблюдатель". Простейший наблюдатель:

    @mozilla.org/xpcom/observer;1 interface nsIObserver

    Это "ничего не делающий" наблюдатель, немного похожий на /dev/null. Его метод observe() просто сообщает об успешном завершении. В Mozilla есть множество специализированных компонентов для наблюдателей, вы также можете создать свой, как показано в листинге 6.7. Источник событий в XPCOM Mozilla более полезен. Он основывается на паре:

    @mozilla.org/observer-service;1 interface nsIObserverService

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

    var observer = { ... as in Listing 6-6 ... }; 
    var observer2 = { ... another observer ... );
    var CC = Components.classes, CI = Components.interfaces; 
    var cls, caster;
    cls = CC["@mozilla.org/observer-service;1"]; 
    caster = cls.getService(CI.nsIObserverService);
    caster.addObserver(observer, "my-event", true); 
    caster.addObserver(observer2, "my-event", true); 
    caster.notifyObservers(caster, "my-event", "anydata");


    Листинг 6.12. Пример XPCOM-пары "источник данных-наблюдатель"

    Объект caster - единственный источник событий. При добавлении наблюдателей с помощью addObserver() первый аргумент - объект наблюдателя, второй - строка события для наблюдателя, третий - флаг, сообщающий, написан ли наблюдатель на JavaScript. Если это так, следует использовать true. Если это XPCOM-компонент, написанный на C/C++, нужно указывать false. notifyObservers() создает событие данного типа; третий аргумент тут - любые данные, которые могут быть аргументами или параметрами события. Эти данные будут получены объектами наблюдателей.

    На этом введение в источники событий и наблюдатели Mozilla заканчивается.

    6.1.5. Команды

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

    Хотя простейшее представление команды - одна функция на JavaScript, это не единственный вариант. Команда также может быть сообщением из одного места в другое. Этот аспект делает команды в Mozilla очень похожими на события. Другие примеры "событийного" поведения появляются поверх базовой системы команд: когда состояние команд меняется, об этом рассылаются уведомления.

    В целом система команд в Mozilla довольно сложна. В главе 9, "Команды", эта система рассматривается не только в контексте целого окна XUL, но и исследуются ее составляющие. Сейчас просто следует запомнить, что система команд Mozilla - расширение или дополнение модели обработки событий DOM 3.

    6.1.6. События доставки данных

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

    События DOM, таймера и наблюдателей - все они имеют дело с маленькими порциями данных. Совершающееся событие может представлять какой-либо другой, больший процесс обработки где-то в платформе Mozilla, но сами данные события обычно невелики по объему. Эти события можно назвать легковесными.

    События доставки данных, по сравнению с описанными выше, обычно большие. Это может быть загрузка электронной почты или загрузка файла на FTP-сервер, или обновление конференции. Более того, такие события могут разбиваться на более мелкие события: отдельные почтовые сообщения, часть файла, загруженная уже на сервер, отдельные заголовки сообщений конференции. Во время работы с такими событиями между поставщиком и потребителем возникают более длительные отношения, которые не заканчиваются, пока последняя часть работы не будет завершена.

    У такой сложной системы событий есть собственный язык. Поставщики здесь называются источниками данных или информации; потребители называются адресатами данных или просто адресатами. Более подробное обсуждение источников и потребителей данных мы отложим до главе 16, "Объекты XPCOM".

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

    6.2. Что происходит при нажатии клавиш

    XUL в Mozilla поддерживает теги <keyset>, <key>, <commandset> и <command>. Эти теги используются для связи клавиш с элементами графического интерфейса и для обработки нажатий клавиш. Эти теги позволяют задавать назначения клавиш для отдельных документов или приложений. XUL также поддерживает атрибут accesskey.

    Клавиша - это маленькая пластмассовая формочка с изображением символов, находящаяся на клавиатуре. Клавиши можно разделить на две группы: те, у которых есть эквивалентные глифы, и те, у которых глифов нет. Глиф - это некоторое визуальное представление, например, А. Стандарт Unicode поддерживает клавиши, которым соответствуют глифы; у сочетаний и клавиш, которым никакие глифы не соответствуют, почти нет стандартной поддержки.

    Поддержка событий нажатия клавиш в Mozilla появилась раньше стандарта DOM 3 Events. Этот стандарт описывает только клавиши, которым глифы не соответствуют. В Mozilla поддержка работы с клавишами включает дополнительные клавиши с глифами. Mozilla и этот стандарт пользуются разными системами нумерации, поэтому значения кодов клавиш в них различны. Как упоминалось ранее, Mozilla поддерживает общие события keypress, keyup и keydown для объекта Document. Эти события поддерживаются не всеми тегами.

    В таблице 6.3 приведены различия между кодами клавиш в двух определениях. Аббревиатура VK означает "виртуальная клавиша".

    Таблица 6.3. Разница между кодами клавиш в Mozilla и DOM 3 Events

    Коды клавиш, определенные в Mozilla

    Коды клавиш, определенные в Mozilla

    Коды клавиш, определенные в DOM 3 Events

    DOM_VK_CANCEL

    DOM_VK_SEMICOLON

    DOM_VK_UNDEFINED

    DOM_VK_HELP

    DOM_VK_EQUALS

     

    DOM_VK_CLEAR

    DOM_VK_QUOTE

    DOM_VK_RIGHT_ALT

     

    DOM_VK_MULTIPLY

    DOM_VK_LEFT_ALT

    DOM_VK_ALT

    DOM_VK_ADD

    DOM_VK_RIGHT_CONTROL

    DOM_VK_CONTROL

    DOM_VK_SEPARATOR

    DOM_VK_LEFT_CONTROL

    DOM_VK_SHIFT

    DOM_VK_SUBTRACT

    DOM_VK_RIGHT_SHIFT

    DOM_VK_META

    DOM_VK_DECIMAL

    DOM_VK_LEFT_SHIFT

     

    DOM_VK_DIVIDE

    DOM_VK_RIGHT_META

    DOM_VK_BACK_SPACE

    DOM_VK_COMMA

    DOM_VK_LEFT_META

    DOM_VK_TAB

    DOM_VK_PERIOD

     

    DOM_VK_RETURN

    DOM_VK_SLASH

     

     

    DOM_VK_BACK_QUOTE

     

    DOM_VK_0 to 9

    DOM_VK_OPEN_BRACKET

     

    DOM_VK_A to Z

    DOM_VK_BACK_SLASH

     

     

    DOM_VK_CLOSE_BRACKET

     

    DOM_VK_NUMPAD0 to 9

     

     

    Необходимо упомянуть и два различия между Mozilla и стандартом:

    Эти различия, фактически, заключаются только в именах.

    6.2.1. Откуда берутся события нажатия клавиш

    Прежде чем стать событием, нажатие клавиши претерпевает ряд изменений. В разделе "Отладка" этой главы описывается, как определить, что преобразование выполняется неверно. Здесь мы просто продемонстрируем процесс сбора событий клавиш.

    Нажатие клавиши начинается с клавиатуры. У каждой физической клавиши есть собственный номер. Этот номер не соответствует ASCII или чему-нибудь еще. Это первое число, создаваемое при нажатии клавиши.

    Клавиатура не так проста, как может показаться. Она взаимодействует с компьютером. Ее встроенное программное обеспечение преобразует нажатие клавиши, ее отпускание или многократное нажатие в скан-код. Скан-код представляет собой одно- или многобайтовое значение. Абсолютного стандарта для скан-кодов не существует, есть лишь хорошо известные реализации, например, клавиатура IBM PC AT 101. Скан-коды тоже не соответствуют ASCII-таблице или чему-нибудь подобному и они отправляются и от клавиатуры, и к ней.

    Некоторые скан-коды отправляются напрямую аппаратной части компьютера, например, когда пользователь при загрузке компьютера нажимает Delete, чтобы попасть в BIOS, или иногда при нажатии Pause или Control-Alt-Delete. Другие коды обрабатываются операционной системой компьютера, которая пытается преобразовать скан-код в код символа: или в ASCII-код, или в Unicode-код, или в какое-то внутреннее представление. В Microsoft Windows здесь вступает действие та часть ПО, которая обозначена пиктограммой клавиатуры на панели управления. В Linux эту работу операционная система выполняет с помощью драйвера.

    Некоторые программы очень сложны. Примеры включают в себя X-Windows и текстовые процессоры с поддержкой иероглифического письма (например, китайского, корейского и японского). Если приложение настолько сложно, ему могут отправляться практически необработанные скан-коды. В X-Windows за соответствие имен, связанных со скан-кодами, и внутренней системы символов X11 ответственна программа xmodmap. В странах, где набор символов больше, чем доступно клавиш на клавиатуре, используются особые системы - методы ввода. При нажатии последовательности клавиш, означающей "Составление символа", появляется маленькое окошко. Содержимое этого окна реагирует на создание клавиш путем нескольких нажатий разных клавиш. В сборках Mozilla с поддержкой иероглифического письма реализован такой метод ввода. Пример - японская иероглифика, содержащая тысячи символов, но все их можно составить, имея клавиатуру с менее чем сотней клавиш.

    6.2.2. Какие клавиши доступны

    После преобразования клавише соответствует стандартный код символа (это может быть стандарт ASCII, Unicode, X11 или внутренний формат операционной системы). С этого момента клавиша сама по себе нам менее интересна, чем так называемые таблицы назначения клавиш, которые связывают отдельные коды символов с какими-либо функциями. Такие таблицы могут добавляться операционной системой, графической средой или отдельными приложениями.

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

    Из всех кодов клавиш, отправляемых приложению, некоторые будут связаны с конкретными командами приложения, например, "открыть окно адресной книги", а некоторые будут совпадать со стандартными функциями графической среды или приложений вообще, например, "сохранить документ". Если автор приложения хочет достичь максимального соответствия соглашениям графической среды, то для особых команд приложения подходит только первая группа кодов клавиш. Функции второй должны совпадать с уже существующими в графической среде таблицами назначения клавиш или не использоваться вообще.

    В таблицах назначения клавиш часто применяются клавиши- модификаторы, например, Control или Alt, или еще какая-нибудь служебная клавиша. Такие модификаторы обычно устанавливаются по соглашению. Они не одинаковы для разных платформ. Mozilla решает эту проблему несовместимости с помощью клавиши быстрого доступа. Эта клавиша связывается с модификатором, который чаще всего используется для текущей платформы. Эту клавишу и пару подобных ей можно определить в пользовательских настройках. Подробнее см. http://www.mozilla.org/unix/customizing.html.

    В платформу Mozilla коды символов попадают через собственную библиотеку графических элементов операционной системы, а затем преобразуются в код клавиши DOM_VK. Такие клавиши связаны с задачами, которое выполняет данное окно Mozilla. Этого можно достичь несколькими способами: с помощью общей таблицы назначения клавиш, назначая командам клавиши напрямую или ставя клавиши в соответствие наблюдателям. Далее описывается каждый из этих методов.

    На самом высоком уровне то, что должна делать клавиша - дело проектирования. Список текущего резервирования клавиш в Mozilla можно найти в документе www.mozilla.org/projects/ui/accessibility/mozkeyplan.html.

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

    6.2.2.1. Таблицы назначения клавиш XBL

    Общие назначения клавиш заданы в Mozilla в XBL-файлах. Существующие примеры находятся не в chrome: это зависящие от платформы данные, хранящиеся в каталоге res/builtin. Есть два файла: общий файл htmlBindings.xml и зависящий от платформы platformHTMLBindings.xml. Эти файлы относятся к XUL и HTML в окнах Mozilla и могут служить отправной точкой для изучения XBL-связей в приложениях. XBL описывается в главе 15, "XBL-связки", но в листинге 6.13 мы приведем короткий пример назначения функции клавише.

    <bindings id="myAppKeys"> 
      <binding id="CmdHistoryKeys"> 
        <handler event="keypress" 
        keycode="DOM_VK_Z" 
        command="cmd_redo" 
        modifiers="accel,shift" 
        /> 
      </binding> 
    </bindings>


    Листинг 6.13. Назначение клавише команды "Повторить" через XBL

    Этот кусок XBL-кода создает группу связей, которая называется myAppKeys, вероятно, для конкретного приложения. Каждая связь в этой группе задает клавиши для какой-либо части приложения. Объединение клавиш в несколько наборов упрощает их повторное использование. В нашем случае набор CmdHistoryKeys указывает все клавиши, участвующие в перемещениях по журналу команд, то есть для операций "Отменить" и "Повторить". Здесь показана только одна такая клавиша.

    Тег <handler> - в этом случае специальный обработчик события onkeypress. Команда, с которой связано нажатие клавиши - cmd_redo. Пока просто заметим, что это функция, реализованная где-то в платформе. Атрибуты keycode и modifiers вместе задают сочетание клавиш, которое нужно нажать, чтобы действие "Повторить" произошло только раз. В нашем примере эта комбинация состоит из трех клавиш: клавиши быстрого доступа платформы, любой клавиши Shift и клавиши Z. В записи, принятой для Microsoft Windows, это комбинация Control-Shift-Z, так как в этой операционной системе клавишей быстрого доступа Mozilla является Control.

    Если атрибут command отсутствует, тогда между открывающим и закрывающим тегами <handler> может находиться JavaScript-код, который будет выполняться при нажатии на клавишу. keycode="DOM_VK_Z" можно заменить на key="z", так как у z есть символьный эквивалент. Если при этом не используются никакие модификаторы, и z, и Z означают "z в нижнем регистре".

    6.2.3. <keybinding>, <keyset> и <key>

    Теги <keybinding>, <keyset> и <key> используются для определения того, как отдельный XUL-документ будет обрабатывать нажатия клавиш. Эти теги используются более конкретно, чем XBL-связи. Тег <key> очень важен и поддерживает следующие атрибуты:

    disabled keytext key keycode modifiers oncommand command

    Если не принимать во внимание атрибут oncommand, тег <key> действует как простой тег для хранения данных без визуального отображения. Это специальный тег, так как Mozilla обрабатывает его содержимое особым образом. У каждого тега <key> должен быть атрибут id, так как именно через него другие теги обращаются к клавише.

    Если значение атрибута disabled - true, тег <key> ничего не будет делать. keytext используется только с тегами <menuitem> и содержит строку, описывающую указанную комбинацию или клавишу. Это описание будет доступно пользователю.

    Атрибут key содержит единственный печатаемый символ, соответствующий клавише. Если этот атрибут отсутствует, проверяется значение keycode - там содержится уже непечатаемый код клавиши с префиксом VK. В XUL такой код клавиши начинается с VK_, а не с DOM_VK_. key или keycode определяют ввод с клавиатуры, которому соответствует тег <key>.

    Атрибут modifiers может быть списком из стандартных клавиш-модификаторов и кроссплатформенных клавиш быстрого доступа, разделенных между собой пробелами или запятыми:

    shift alt meta control accel access

    Если приложение должно быть кроссплатформенным, рекомендуется использовать accel. Модификатор access означает, что пользователь нажал специально определенную для данного тега клавишу доступа.

    В атрибут oncommand можно поместить JavaScript-обработчик, который будет запускаться при нажатии клавиши. При использовании тега <key> значение этого атрибута немного меняется по сравнению с тем, что описано в главе 9, "Команды". В случае <key> oncommand действительно запускается при нажатии на клавишу, как и можно было предположить.

    <keyset> - тег-контейнер вроде <stringbundleset> или <broadcasterset>. У него нет собственных особых свойств. Используйте его при аккуратном оформлении набора из нескольких тегов <key>.

    <keybinding> - ненастоящий XUL-тег. В chrome Mozilla некоторые определения клавиш хранятся в виде отдельных .xul-файлов. В качестве корневого тега в них используется не <window>, а <keybinding>. Это всего лишь удобное соглашение без особого значения. Такие связи для клавиш добавляются к другим XUL-файлам и никогда не отображаются в собственных окнах.

    Чаще всего тег <key> ассоциируется с документами-формами, и вот почему. Если этот тег присутствует, при нажатии на соответствующую клавишу запустится ее обработчик oncommand, причем это произойдет независимо от того, где в окне документа находится указатель мыши. Правда, есть одно ограничение. В документе должен быть хотя бы один элемент формы, и у одного из элементов формы должен быть фокус. Это означает, что, по крайней мере, один из следующих тегов должен присутствовать и получить фокус, прежде чем нажатия клавиш начнут срабатывать:

    <button> <radio> <checkbox> <textbox> <menulist>

    Это ограничение можно обойти, спрятав с помощью стилей один- единственный тег с фокусом так, чтобы он вообще никак не отображался.

    Есть и другие XUL-теги и атрибуты, относящиеся к <key>. Атрибут accesskey, описываемый в главе 7, "Формы и меню", определяет клавишу доступа для отдельного тега. Тег <key> также тесно связан с тегом <menuitem> в выпадающих меню.

    Следующие имена атрибутов используются для временного хранения данных в тегах <key>, но у них больше нет особого значения - если оно и было когда-то. Они перечислены здесь, так как время от времени встречаются в chrome Mozilla. Не стоит обращать на них внимания:

    shift cancel xulkey charcode
    6.2.4. XUL-атрибут accesskey

    XUL предоставляет одну возможность для более комфортной работы пользователей с ограниченными физическими возможностями. Атрибут accesskey можно добавлять ко многим тегам, участвующим в создании форм и меню, в том числе <button> и <toolbarbutton>. Синтаксис этого атрибута такой же, как и у атрибута ; другими словами, в качестве его значения указывается печатаемый символ:

    accesskey="K"

    Нажатия клавиш, указанных в атрибутах accesskey, обрабатываются, только если включена система расширенного доступа. Она включается либо через специальное оборудование, подключенное к компьютеру, например, особые устройства ввода, либо по нажатию клавиши Alt. Эта система кратко описывается в главе 8, "Навигация", но подробно в курсе не рассматривается.

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

    6.2.5. Поиск по мере набора (опережающий поиск)

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

    Поиск по мере набора начинается с нажатия на клавишу /. С этого момента все вводимые печатаемые символы "склеиваются" в одну строку. Эта строка ищется в тексте гиперссылок на текущей странице. Нажатие на Enter/Return приводит к переходу по текущей ссылке, соответствующей строке поиска. Это напоминает механизм поиска, использующийся в UNIX-программах вроде emacs.

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

    Механизм поиска "собирает" нажатия клавиш во время фазы возврата при обработке DOM-событий. Чтобы предотвратить это, нужно вызвать для события preventDefault() после того, как ваш собственный обработчик нажатия клавиш завершился.

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

    6.3. Что происходит при движениях мыши

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

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

    Тривиальные жесты мышью обрабатываются в Mozilla соответственно DOM 3 Events. Эти события - простейшие (атомарные) операции, из которых составляются более сложные жесты; они перечислены в таблице 6.2. Пользуйтесь ими, как и любыми другими DOM-событиями. Поддержка более сложных жестов рассматривается в дальнейших разделах.

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

    В большинстве случаев сложные жесты мышью должны быть реализованы в JavaScript. Хотя Mozilla может перехватывать и обрабатывать поток событий mousemove в JavaScript удивительно быстро, такая обработка расходует большое количество ресурсов процессора. Плохо реализованная (или даже хорошо реализованная) поддержка жестов мышью в JavaScript может быть слишком требовательна к процессору в случае старых компьютеров.

    6.3.1. Выделение

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

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

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

    1.       нажатие левой кнопки мыши в начальной точке выделения;

    2.       перемещение мыши к конечной точке выделения, удерживая кнопку мыши нажатой;

    3.       отпускание левой кнопки мыши в конечной точке выделения.

    Однако это правило выполняется не всегда. Совсем другой жест с тем же эффектом может быть таким:

    1.       нажатие и отпускание левой кнопки мыши в начальной точке выделения;

    2.       перемещение мыши к конечной точке выделения;

    3.       перемещение мыши к конечной точке выделения;

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

    Выделение текста работает внутри HTML-тегов <input type="text"> и <textarea> и XUL-тега <textbox>. Элемент DOM 1 для этих тегов содержит свойства и методы для просмотра и задания выделения:

    Сюда относится также свойство user-select из черновика CSS 3.

    Выделение данных вообще (за исключением элементов управления) реализовано только для HTML. Отправной точкой в изучении выделений в HTML может послужить метод document.getSelection(), который возвращает объект, содержащий массив из объектов Range. Объекты Range определены в стандарте DOM 2 Traversal and Ranges. По умолчанию пользователь не может выделить мышью XUL-данные. Однако эту функциональность можно добавить. Начать можно с метода createRange(), относящегося к объекту XUL-документа. Этот метод создает DOM-объект Range, который можно изменить так, чтобы он соответствовал любой непрерывной части дерева XUL-документа. При перемещении мыши этот объект можно динамически обновлять, а к поддереву, представляющему выделение, можно также динамически применять правила стилей для подсветки выделяемых данных.

    И в HTML, и в XUL этот тип выделения ограничивается выделением целых, непрерывных строк данных (другими словами, блоков строк, см. лекцию 2, "Проектирование с XUL"), за исключением первой и последних строк, которые могут быть выделены частично. Исключение из этого правила - вертикально отображаемый текст, как в иврите или китайском, а также столбцы HTML-таблиц (см. "Множественное выделение").

    Если вы решитесь поэкспериментировать с DOM-объектом Range, помните, что хотя он и полностью реализован, в Mozilla он используется не так долго и тщательного тестирования не проходил.

    6.3.1.1. Выделение прямоугольником

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

    В Mozilla поддержка такого выделения отсутствует вообще, но платформа предоставляет достаточно возможностей для ее реализации. Это выделение может поддерживаться и в HTML, и в XUL. Здесь мы обсудим XUL-вариант.

    Так как в XUL не поддерживаются слои и CSS-стили z-index, создать сам прямоугольник для выделения проблематично. Решение - убедиться, что все содержимое XUL-документа находится в одной карте тега <stack> (см. лекцию 2, "Проектирование с XUL"). Прямоугольник для выделения - это тег <box> без содержимого и границами с определенным стилем. Он один находится в своей карте стека. В обычных условиях у этой второй карты есть стиль visibility:none.

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

    6.3.1.2. Множественное выделение

    Множественное выделение более всего знакомо благодаря окнам рабочего стола с пиктограммами. В Microsoft Windows или, например, GNOME несколько пиктограмм могут быть выделены щелчком левой кнопкой мыши с удерживанием Control по каждой из них. Этот способ отличается от выделения прямоугольником, когда вся область выделяется за раз.

    Mozilla поддерживает множественное выделение в тегах <listbox> и <tree>. Описание работы этих тегов можно найти в главе 13, "Списки и деревья". Для множественного выделения здесь используется щелчок с удерживанием Control.

    Mozilla также поддерживает множественное выделение в HTML для вертикально ориентированного текста. Поддержка такого текста, который называется BiDi-текстом (bi-directional, двунаправленный) в XUL еще отсутствует. Жест для этого вида выделения такой же, как и для обычного выделения данных, но обработка происходит по-другому. При использовании фабричного метода getSelection() для объекта HTML- документа вместо одного объекта Range создается их массив. Каждый из этих объектов отражает одну вертикальную полосу (один столбец выделенного содержимого). Метод поиска по индексу getRangeAt() возвращает эти объекты.

    Привычное выделение по щелчку мыши с удерживаемым Control в Mozilla реализовать легко. События keydown и keyup при нажатии, а затем отпускании Control можно использовать для определения начала и конца жеста. События щелчка мышью можно использовать для определения выделенных объектов во время жеста. Завершение одного щелчка совсем не значит, что жест закончился. Жест заканчивается тогда, когда программист говорит, что он закончился.

    6.3.2. Перетаскивание

    Перетаскивание - это жест, в котором визуальный объект выбирается и перемещается вместе с указателем мыши, пока он не будет отпущен. Дополнительной особенностью этого жеста может быть использование целевых областей. Целевая область - это такая часть окна, в которой операция перетаскивания может завершиться успешно. Если эти области существуют, при "пронесении" над ними объекта они должны подсвечиваться. Классический пример - пиктограмма мусорной корзины в Macintosh. При помещении пиктограммы документа поверх нее эта корзина темнеет. Если используются целевые области, обычно перетаскиваемый объект после успешного перемещения исчезает из вида.

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

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

    Поддержка перетаскивания в Mozilla - это мозаика из нескольких кусочков.

    Первый кусочек мозаики - события и их обработчики. Для простого перетаскивания необходимы три события: draggesture (начало перетаскивания), dragover (эквивалент mouseover) и dragdrop (конец перетаскивания). Два дополнительных события используются в более сложных случаях, когда перемещаемый объект попадает в окно Mozilla извне или, наоборот, выходит за его пределы. Это события dragenter и dragexit.

    Второй кусочек - XPCOM-объекты. Самая важная пара "компонент-интерфейс":

    @mozilla.org/widget/dragservice;1 nsIDragService

    Это та часть Mozilla, которая отвечает за жест в процессе перетаскивания.

    Вызов метода invokeDragSession() интерфейса nsIDragService начинает сеанс перетаскивания. В особых случаях этот метод может принимать объект nsIScriptableRegion. Данный объект - набор прямоугольников в виде их координат в пикселах. Это нужно в тех случаях, когда область, над которой происходит перетаскивание, включает сложный элемент управления, состоящий полностью из графических элементов низкого уровня. Набор прямоугольников определяет активные участки (возможные целевые области) этого элемента управления, которые обрабатываются самим элементом, если указатель мыши попадает на любой из этих участков. Такая система позволяет пользователю получать реакцию от элемента управления, с которым нельзя работать из скриптов или если скрипты для него отсутствуют. Данная методика полезнее всего во встроенных системах и не используется в платформе Mozilla, если не задействуется тег <tree>. В большинстве случаев вместо объекта nsIScriptableRegion следует использовать null.

    Метод getCurrentSession() возвращает объект nsIDragSession. Этот объект - простой набор состояний о текущем жесте перетаскивания и собственных методов не содержит. В каждом окне Mozilla за раз может происходить только одна операция перетаскивания объекта.

    Третий кусочек мозаики - поддержка JavaScript. Хотя управление жестом перетаскивания и реализовано, результат жеста (подразумеваемая команда) должен быть написан программистом самостоятельно. В простом случае это означает использование интерфейсов nsIDragService и nsIDragSession в начале жеста, определение начальной и конечной точек жеста, предоставление стилей или другой реакции во время выполнения жеста, а также выполнение в конце команды, подразумеваемой жестом.

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

    В целом, написание скриптов для реализации перетаскивания - нетривиальная задача. Для упрощения процесса доступны два объекта JavaScript.

    Файл chrome nsDragAndDrop.js из архива toolkit.jar реализует объект nsDragAndDrop, который устанавливается как свойство объекта окна. Этот объект предоставляет методы, делающие почти всю описанную выше черную работу, включая особую поддержку XUL-тега <tree>. Для этого файла нет XPIDL-определения - есть только то, что присутствует в самом .js-файле. Для каждого из событий перетаскивания существует соответствующий метод.

    Второй объект должен создаваться автором приложения. Это обычный объект JavaScript, у которого также нет XPIDL-определения. Он должен создаваться соответствующими методами. Этот объект - набор обработчиков событий, по одному на каждое событие. Независимо от того, какому шаблону он может следовать, объект такого типа создается и присваивается переменной, обычно с именем вроде myAppDNDObserver.

    Вместе эти два объекта сокращают размер каждого требуемого обработчика перетаскивания до одной строки скрипта. Сначала событие обрабатывается общим кодом nsDragAndDrop, а затем - любым специальным кодом, предоставляемым myAppDNDObserver. Чтобы посмотреть, как работает эта система, взгляните на простой пример в адресной книге Mozilla. Обратите внимание на использование abResultsPaneObserver (который мог бы называться abResPaneDNDObserver) в messenger.jar в chrome.

    В таблице 6.4 показаны соответствия между этими удобными методами и базовыми событиями.

    Таблица 6.4. Соответствия между событиями перетаскивания и методами скриптов

    Событие

    метод nsDragAndDrop

    метод -DNDObserver

    Draggesture

    startDrag()

    onDragStart()

    Dragover

    dragOver()

    onDragOver()

    Dragdrop

    drop()

    onDrop()

    dragexit

    dragExit()

    onDragExit()

    dragenter

    dragEnter()

    onDragEnter()

    6.3.3. Изменение размеров окна

    Изменения размеров окон - следствие жеста мыши, реализованного в Mozilla на C/C++. См. описание тега <resizer> в главе 4, "Первые элементы управления и темы".

    6.3.4. Расширенная поддержка жестов

    Жесты мышью могут быть менее формальными, чем описанные выше примеры. Как дирижер оркестра взмахивает палочкой, особенные движения и щелчки мышью могут заставить классический браузер выполнять команды меню вроде "Добавить в закладки", "Назад" или "Сохранить". Поддержка этих жестов реализована и для браузера Mozilla.

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

    Проект Optimoz предоставляет такую систему жестов, полностью реализованную на JavaScript. Ее можно найти на сайте www.mozdev.org. Код этого расширения небольшой и понятный. Как и в случае опережающего поиска, система жестов перехватывает события во время фазы возврата, избегая столкновения с другими потребителями тех же самых событий.

    6.4. Альтернатива: таблицы стилей

    Специальных стилей, влияющих на обработку событий, в Mozilla нет. Некоторые из псевдостилей CSS 2 вроде :active используются для текущего элемента в фокусе или текущего выделенного элемента.

    6.5. Практика: ввод данных в NoteTaker

    В этот раз мы добавим в диалог NoteTaker поддержку работы с клавиатуры и несколько обработчиков событий. Дальнейшее обсуждение обработчиков событий приводится в главе 13, "Списки и деревья". Здесь мы будем следовать систематическому подходу. В текущем разделе будет представлен простой вводный код.

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

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

    Для этого мы изменим все кнопки в диалоге так, как показано в листинге 6.14.

    // до 
    <toolbarbutton label="Edit" onclick="action('edit');"/> 
    <toolbarbutton label="Keywords" onclick="action('keywords');"/> 
    <button label="Cancel"/> <button label="Save"/>
    // после 
    <toolbarbutton label="Edit" accesskey="E" onclick="action('edit');"/> 
    <toolbarbutton label="Keywords" accesskey="K" 
      onclick="action('keywords');"/> 
    <button label="Cancel" accesskey="C"/> 
    <button label="Save" accesskey="S"/>


    Листинг 6.14. Добавление accesskey в NoteTaker

    Мы можем продолжить наши изменения. Мы можем подсвечивать символы и в области редактирования, используя тег <label>. Это означает изменение данной строки:

    <description>Summary</description>

    на эту:

    <label value="Summary" accesskey="u"/>

    В этом случае мы не можем для Summary использовать S, так как эта буква уже используется для Save. Когда мы сделаем такие изменения для всех заголовков, наше окно будет выглядеть так, как на рисунке 6.3.

    Диалог NoteTaker с подсвеченными клавишами доступа


    Рис. 6.3.  Диалог NoteTaker с подсвеченными клавишами доступа

    Но после внесения изменений нажатие на эти клавиши ни к чему не приводит. Теперь нам нужно добавить несколько тегов <key>, как показано в листинге 6.15.

    <keyset> 
      <key key="e" oncommand="action('edit')"/> 
      <key key="k" oncommand="action('keywords')"/> 
      <key key="c" oncommand="action('cancel')"/> 
      <key key="s" oncommand="action('save')"/> 
      <key key="u" oncommand="action('summary')"/> 
      <key key="d" oncommand="action('details')"/> 
      <key key="o" oncommand="action('options')"/> 
      <key key="z" oncommand="action('size')"/> 
    </keyset>


    Листинг 6.15. Связывание клавиш доступа с действиями в NoteTaker

    Атрибут key не зависит от регистра; если нам требуется именно заглавная S, нужно добавить атрибут modifiers="shift". Если мы хотим использовать F1, следует задействовать атрибут keycode и виртуальный символ вместо key и печатаемого символа. Мы можем использовать код, созданный ранее, так как функция action() принимает как аргумент единственную инструкцию. Если изменить action() так, чтобы она включала такую строку:

    alert("Action: " + task");

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

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

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

    .disabled { 
      border : solid; padding : 2px; margin : 2px; 
      border-color : darkgrey; color : darkgrey 
    }

    На панели правки есть четыре области (Summary, Details, Options и Size) и в сумме восемь отображаемых блоков, каждый из которых, возможно, требуется обновить. Мы можем сгруппировать эти восемь блоков в четыре с помощью тегов <broadcaster> и атрибутов observes. Мы добавляем источники событий в листинге 6.16, который для начала ничего не указывает.

    <broadcasterset> 
      <broadcaster id="toggle-summary"/> 
      <broadcaster id="toggle-details"/> 
      <broadcaster id="toggle-options"/> 
      <broadcaster id="toggle-size"/> 
    </broadcasterset>


    Листинг 6.16. Источники событий для отключения частей диалога NoteTaker

    Мы добавим всем восьми блокам атрибут observes=, связывая каждый из них с одним из четырех источников событий, так что

    <box id="dialog.top" class="temporary">

    становится

    <box id="dialog.top" class="temporary" observes="toggle-size"/>

    Если у любого из тегов <broadcaster> появится атрибут class="disabled", этот атрибут будет передан блокам, наблюдающим за ним, и их стиль изменится. Мы можем проверить эту систему, временно задав class="disabled" любому тегу <broadcaster>. Если сделать это для последнего из них ("toggle-size"), диалог будет выглядеть так, как на рисунке 6.4.

    Диалог NoteTaker с частью "отключенных" параметров


    Рис. 6.4.  Диалог NoteTaker с частью "отключенных" параметров

    Так как этот код работает, мы теперь можем связать его с пользовательским вводом. Сначала - клавиши. Для этого мы подправим метод action(). В листинге 6.17 показан новый код.

    function action(task) {
      if ( task == "edit" || task == "keywords" ) { 
        var card = document.getElementById("dialog." + task);        
        var deck = card.parentNode;
        if ( task == "edit" ) deck.selectedIndex = 0; 
        if ( task == "keywords") deck.selectedIndex = 1; 
      } 
     
      if ( task == "summary" || task == "details" || 
       task == "options" || task == "size" ) { 
        var bc = document.getElementById("toggle-" + task);
        var style = bc.getAttribute("class"); 
          if ( style == "" || style == "temporary" ) 
           bc.setAttribute("class","disabled"); 
        else 
        bc.setAttribute("class","temporary"); 
      } 
    }


    Листинг 6.17. Код для изменения стиля частей диалога NoteTaker

    Этот код меняет только теги источников событий, а платформа берет на себя все остальное. Если необходимо, мы можем проверить этот код, напрямую вызывая метод action(), возможно, из обработчика onload тега <window>. Это так же легко проверить, нажимая соответствующую клавишу.

    Поддержка мыши несколько сложнее. Где же именно в окне пользователь должен щелкнуть мышью, чтобы проявился какой-то эффект? Мы могли бы создать обработчик событий для каждого тега, но это не очень разумно. Вместо этого мы воспользуемся моделью размещения XUL-элементов и моделью DOM-событий. Мы убедимся, что каждая из четырех частей окна хранится в отдельном XUL-теге - это то, что касается размещения. А вверху дерева документа создадим обработчик, который будет перехватывать события onclick и определять, к какой из частей это событие относится - это DOM-события.

    Прежде всего, обратимся к XUL-части задачи. Разделы Summary и Details не находятся в одном блоке, так что мы поместим каждую пару <label> и <box> в дополнительный <vbox>. С точки зрения отображения это необязательно, но для нашего скрипта - удобно. Теперь у нас есть две части окна, содержащиеся в тегах <vbox>, и две - в тегах <groupbox>. Каждому из этих четырех тегов мы добавим атрибут subpanel с именем соответствующего раздела:

    <groupbox subpanel="size"/>

    В XUL у этого атрибута нет какого-либо особого значения; мы только что придумали его сами. Это удобно размещенные данные.

    Далее мы напишем функцию обработки событий, которая будет перехватывать щелчки мышью. Мы хотим перехватывать их с самого начала, то есть в фазе перехвата. XUL-атрибут onclick годится только для фазы возврата, так что используем addEventListener() из JavaScript. Функция обработчика и установка обработчика показаны в листинге 6.18.

    function handle_click(e) {
      var task = ""; 
      var tag = e.target; 
      while ( (task = tag.getAttribute("subpanel")) 
        == "" && tag.tagName != "window" ) 
      tag = tag.parentNode; 
      if ( task != "") 
      action(task); 
    }
    document.addEventListener("click", handle_click, true);


    Листинг 6.18. Код для перехвата событий щелчка мышью и определения соответствующего действия

    Так как этот обработчик устанавливается для объекта Document (тег <window>) и будет запускаться в фазе перехвата, он будет получать события onclick раньше всех. Он смотрит на переданный объект события DOM 2 и определяет цель этого события. Эта цель - самый глубоко вложенный тег из всех оказавшихся под указателем мыши. Затем код идет вверх по DOM-дереву от этой цели в поисках тега с атрибутом subpanel. Если он находит такой тег, запускается действие со значением этого атрибута. Если такой тег не найден, ничего не происходит. Затем обработка события в дереве продолжается обычным образом.

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

    На этом мы пока завершим практику. Мы не сохраним все внесенные здесь изменения, но в целом это все равно были полезные эксперименты.

    6.6. Отладка: обнаружение событий

    В Mozilla обнаружение событий - тривиальный процесс.

    Во-первых, необходимо убедиться, что значение настройки browser.dom.window.dump.enabled - true. Следует запустить платформу с ключом -console. Во-вторых, нужно написать однострочную функцию диагностики:

    function edump(e) { dump(e.type+":"+e.target.tagName); }

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

    Чтобы определять широковещательные события, нужно создать обработчики для события oncommand или просто добавить наблюдатели для объекта nsIObserverService. На данный момент в стандартной платформе нет возможности наблюдать за всеми именами изменений, которые рассылаются этими глобальными источниками событий.

    В версии платформы для отладки можно создавать файл журнала всех широковещательных событий с помощью следующих переменных окружения (см. подробнее в файле исходных текстов prlog.h):

    set NSPR_LOG_MODULES=ObserverService:5 set NSPR_LOGFILE=output.log
    6.6.1. Проблемы с клавиатурой

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

    На самом нижнем уровне может быть повреждена физическая клавиша или ее контакт. Проверьте клавишу в других приложениях (например, в vi войдите в режим вставки - i - и нажмите Control-V, а затем нажмите нужную клавишу).

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

    Чтобы проверить, получает ли операционная система код клавиши корректно, запустите Windows в режиме только DOS или зайдите в Linux из терминала без запуска X11. В Windows для проверки отличных от ASCII символов можно использовать редактор Edit в символьном режиме.

    Чтобы прояснить проблемы при использовании раскладок других языков, лучше всего обратиться за помощью к носителям языка. Вежливую просьбу можно поместить в одну из USENET-конференций soc.culture. Поддержка некоторых языков хуже, чем других, и пользователи, владеющие этими языками, могут прийти на помощь. В базе данных ошибок Mozilla, Bugzilla (http://bugzilla.mozilla.org), можно найти множество обсуждений вопросов интернационализации, совместимости и локализации.

    Чтобы проверить, доходит ли до приложения нажатие клавиши, воспользуйтесь эмулятором терминала (например, DOS box, xterm или gnome-terminal) в режиме "raw". Этот режим доступен при запуске редактора вроде Edit или vi в таком терминале. Эмулятор терминала при получении ввода пользуется функциями графической среды (или менеджера окон, по крайней мере).

    Чтобы проверить, получает ли нажатие клавиши Mozilla, просто добавьте подписчик на события для нужной клавиши объекту document окна chrome.

    6.7. Итоги

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

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

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

    Простая обработка событий достаточно примитивна. Большая часть событий связана с каким-нибудь полезным элементом управления. В следующей главе мы рассмотрим самые полезные элементы управления Mozilla: формы и меню.

     


    об авторе

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





    Сайт создан в системе uCoz