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

    В этой главе рассматривается система команд Mozilla. Система команд служит для отделения функциональности приложения от его внешнего вида. Она используется отдельно от системы событий DOM, хотя события и команды могут взаимодействовать.

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

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

    Традиционные приложения на основе HTML часто состоят из самой запутанной части JavaScript-кода. Систему команд также можно рассматривать как попытку избавиться от этого неструктурированного подхода.

    Иллюстрация в начале этой главы демонстрирует те части Mozilla, которые реализуют команды. Из рисунка видно, что система команд строится поверх DOM и подсистемы событий DOM, но также требует доступа к определенному числу XPCOM-компонентов. Самые важные компоненты также появляются как AOM-объекты. В XUL-языке есть теги, поддерживающие систему команд, но у них нет визуального представления; в этом отношении они похожи на тег [m]<key>[/m]. Система команд идет дальше тега [m]<key>[/m], потому что у нее нет никакого пользовательского интерфейса. Система команд по отношению к платформе полностью внутренняя.

    9.1. Команды и Mozilla

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

    Система команд Mozilla спроектирована для поддержки сложных приложений. Простым приложениям команды не нужны; они могут обойтись и простыми обработчиками событий. Для сложных приложений целью Mozilla было предоставить систему, в которой:

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

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

    Обнаружить команду в терминах исходного кода не так легко. Самый очевидный признак команды - простая строка, чье имя - имя команды. Такие имена могут определяться платформой или программистом приложения. К сожалению, остальная часть инфраструктуры команд довольно сильно разбросана по платформе. Части системы команд есть в XUL, JavaScript, XPCOM, существующих файлах chrome и внутренностях платформы.

    9.1.1. Hello, World

    Простой пример использования системы команд приведен в листинге 9.1.

    <?xml version="1.0"?>
    <!DOCTYPE window> 
      <window 
    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
      <command id="hello" oncommand="alert('hello, world');"/>
      <button label="Say It" command="hello"/>
    </window>
    
    Листинг 9.1. Пример "hello, world", реализованный как команда Mozilla

    Тег <command> реализует команду, в данном случае простой обработчик событий, который вызывает alert(). Тег <button> идентифицирует эту команду по имени. Когда мы нажимаем кнопку, генерируется событие DOM 2 специального типа command, и идентифицированная команда захватывает это событие и запускается. Результатом будет появление окошка alert().

    Команды не всегда столь просты, и не всегда основаны на обработке события. Если реализовать эту команду на JavaScript, эффект, вызываемый кодом листинга 9.2, будет тот же, что и в листинге 9.1.

    <?xml version="1.0"?> 
    <!DOCTYPE window> 
    <window 
      xmlns= "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 
      <script> 
        var control = { 
          supportsCommand : function (cmd) { return true; }, 
          doCommand : function (cmd) { alert(cmd + ", world"); }, 
          isCommandEnabled : function (cmd) { return true; }, 
          onEvent : function (event_name) {} 
        }; 
        window.controllers.appendController(control); 
    
        function execute(cmd) { 
          var disp = document.commandDispatcher 
          var cont = disp.getControllerForCommand(cmd); 
          cont.doCommand(cmd); 
        } 
      </script> 
      <button label="Say It" onclick="execute('hello')"/> 
    </window>
    
    Листинг 9.2. Пример hello, world, реализованный как команда на JavaScript.

    Этот код описывает объект control, реализующий команду; фактически, данный объект может реализовать несколько команд. Команда реализована в методе doCommand(). Затем объект нужно встроить в инфраструктуру команд платформы. Он будет вызываться каждый раз, когда потребуется выполнить данную команду. И, наконец, команда может быть вызвана в любом месте документа XUL с помощью функции execute(). В данном случае удобно использовать обработчик onclick.

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

    9.2. Концепция команд

    Система команд Mozilla - это система распространения команд. Реальные команды реализуются прикладным программистом. Создать и запустить команду гораздо проще, чем понять, как они вызываются, находятся и выполняются.

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

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

    Последний абзац означает, что команды не "удалены" от прикладного программиста. Они часть того кода, который он создает и подключает к XML-документу, как и любое иное содержание.

    Mozilla использует специальный дизайн кода, называемый Command pattern, "образец команды", чтобы отделить имена команд от их реализации. Большинство современных пакетов разработки GUI включают технологии, подобные Command pattern. Ниже будет показано, как работают такие образцы.

    9.2.1. Шаблон проектирования "функтор"

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

    // Plain function function 
    halve_function(num) { return num / 2.0; } 
    
    // Functor var num = null; 
    var halve_functor = { 
      enabled : true, 
      exec : function () { num /= 2.0; return true; } 
    } 
    
    // Examples of use 
    num = halve_function(23);  // sets num = 11.5
    num = 23; 
    halve_functor.exec(); // sets num = 11.5
    
    Листинг 9.3. Объект функтор и функция, выполняющие единственную операцию.

    Объект functor не только кажется необоснованно сложным по сравнению с простой функцией, но он вдобавок требует лишней глобальной переменной для своей работы. Однако он гораздо более гибок, поскольку имеет стандартизованный интерфейс. Все functor-объекты имеют единственный метод exec(), выполняющий реализованную функтором команду и возвращающий лишь сообщение об ошибке или об успешном выполнении. Таким образом, все функторы для программиста выглядят одинаково.

    Объект functor может хранить информацию о состоянии. В листинге 9.3 приведен пример очень распространенного параметра состояния enabled. На практике большинство систем, реализующих команды, включая Mozilla, в явном виде поддерживают состояние enabled. Прикладной код может опрашивать это состояние, чтобы узнать, доступна ли команда, и затем реагировать соответствующим образом. Объект functor может хранить любую информацию состояния о команде, какая представляется удобной, а не только состояние enabled. Другими состояниями могут быть имя команды, уникальный id, флаги, означающие, готова ли команда к выполнению, блокирована или оптимизирована; язык, на котором команда реализована, или ее версия и т.д.

    В данном примере объект functor модифицирует глобальную переменную num при выполнении команды. Это позволяет методу exec() не иметь параметров, что делает все объекты functor в этом отношении одинаковыми. num может быть свойством объекта functor, но это неудачное решение. Это плохо, потому что состояние функтора не должно зависеть от его выполнения. Хотя он содержит информацию о состоянии (свойство enabled), реализация команды не использует информацию о состоянии при выполнении. Информация о состоянии используется кодом, который вызывает функтор.

    Функтор в приведенном примере может быть реализован и по-другому. Изменим метод exec() так:

    exec: function () { return really_halve_it(); }
    

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

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

    В литературе о функторах, не связанной с Mozilla, метод exec() часто обозначают как execute(). В Mozilla proxy-объект (функтор) команды называют обработчиком команды. Обычно они не слишком часто используются, потому что удобнее применять концепцию более высокого уровня - контроллер команды.

    9.2.2. Шаблон команды и контроллеры

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

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

    Чтобы реализовать шаблон команды, нужно создать объект, содержащий набор функторов. Когда пользователь передает этому объекту имя команды, следует вызвать соответствующий объект-функтор. Это и есть шаблон команды. В листинге 9.4 приведен пример такого объекта, называемого в Mozilla контроллером. Данный пример реализует простой светофор.

    // functors 
    var stop = { exec: function () { top.light = "Red"; }; 
    var slow = { exec: function () { top.light = "Amber"; }; 
    var go = { exec: function () { top.light = "Green"; }; 
    // controller containing functors 
    var controller = { 
      _cmds: { stop:stop, slow:slow, go:go 
      }, 
      supportsCommand : function (cmd) { 
        return (cmd in this._cmds); 
      }, 
      doCommand : function (cmd) { 
        return _cmds[cmd].exec(); 
      }, 
      isCommandEnabled : function (cmd) { 
        return true; 
      }, 
      onEvent : function (event_name) { 
        return void; 
      } 
    }; 
    // set the light to green 
    controller.doCommand("go");
    
    Листинг 9.4. Объект Controller, реализующий светофор

    Объект controller содержит объект _cmds, устанавливающий соответствие между именами свойств и функторами. В языках с полным сокрытием информации, наподобие C++, такой объект был бы назван внутренним. Три имеющиеся функции обрабатывают конкретное имя команды, полученное в виде строки. supportsCommand() сообщает, знает ли контроллер о команде; doCommand() выполняет функтор данной команды; onEvent() передает имя события, которое может быть для данной команды значимо.

    Последний метод - isCommandEnabled() в данном случае является некоторым жульничеством. Он должен проверять свойство enabled у соответствующего функтора, но данный контроллер знает, что все три лампочки светофора всегда доступны, - поэтому просто возвращает true. Это и потому также так, что ни один из приведенных функторов в листинге 9.4 не реализует свойство enabled. Кажется, что они должны реализовать три полноценных функтора, но фактически они реализуют лишь то, что требуется контроллеру.

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

    var controller = { 
      supportsCommand : function (cmd) { 
        return (cmd=="red" || cmd=="amber" || cmd=="green"); 
      }, 
      doCommand : function (cmd) { 
        if ( cmd == "red" ) top.light = "Red"; 
        if ( cmd == "amber" ) top.light = "Amber"; 
        if ( cmd == "green" ) top.light = "Green"; 
      }, 
      isCommandEnabled : function (cmd) { 
        return true; 
      }, 
      onEvent : function (event_name) { 
        return void; 
      } 
    }; 
    // set the light to green 
    controller.doCommand("go");
    
    Листинг 9.5. Объект controller, не имеющий отдельных функторов

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

    Сложные контроллеры также могут многое выиграть от явной реализации команды. Иногда возникают проблемы с синхронизацией, которые лучше всего решить в контроллере. Пример - реализация команды Undo в редакторах, подобных Classic Composer. Контроллер отвечает за реализацию конкретной команды, так что его логично использовать и для сохранения истории команд. Если нужно выполнить Redo или Undo, контроллер может прочитать верхний элемент истории команд и вызвать соответствующую команду, реализующую этот запрос. Все подобные механизмы с историей состояний можно сначала реализовать вне контроллера, но затем спрятать в контроллере так, чтобы сохранить главную цель существования контроллера - управление (controlling). Контроллеры также удобны для размещения в них макро-языков, механизмов синхронизации, и всего, что связано с существованием команд.

    9.2.3. Место расположения контроллера

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

    Все контроллеры должны быть помещены туда, где платформа сможет их найти. Их можно поместить в несколько разных мест.

    Контроллеры можно поместить в объект window.

    Несколько XUL-тегов являются подходящими местами. Можно использовать <button>, <checkbox>, <radio>, <toolbarbutton>, <menu>, <menuitem>, и <key>.

    XUL-тег <command> подобен функтору, когда он явно реализует команду, используя обработчик события. Если такой тег создан, платформа добавит функтор к постоянному внутреннему контроллеру.

    Использование каждого из этих мест будет рассматриваться позднее, при обсуждении синтаксиса команд.

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

    9.2.4. Диспетчеры и поиск и выполнение команды

    Каждый контроллер содержит набор команд; в одном месте может быть ноль или более контроллеров; диспетчер контроллеров работает с набором таких мест. К счастью, это все. Задачей диспетчера контроллеров является поиск и выполнение конкретной команды.

    Mozilla имеет один диспетчер для HTML-документов и один - для документов XUL. Диспетчер HTML невидим и недоступен скриптам ни при каких обстоятельствах. С диспетчером команд XUL можно работать. Каждое окно XUL-приложения имеет объект dispatcher в корне дерева документов, и этот объект всегда присутствует и доступен.

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

    Диспетчер использует информацию о состоянии текущего фокуса, чтобы решить, какой контроллер нужен для данной команды. Эта информация о состоянии включает состояние фокуса окна, все фокусы тегов <iframe>, и любой элемент DOM в фокусном кольце, если он имеет в настоящее время фокус. Другими словами, диспетчеру нужна информация об иерархии фокуса. Диспетчер начинает с наиболее глубокого, специфичного положения фокуса (обычно это элемент формы или меню), и переходит по дереву DOM вверх вплоть до верхнего окна. На каждом элементе DOM, способном получить фокус, он исследует цепочку контроллеров в поисках объектов-контроллеров. Далее он исследует поддержку нужной команды, запрашивая поддерживаемый контроллером метод Command(). Первый же объект controller, реализующий выполнение нужной команды, возвращается. Если на данном уровне результата не находится, диспетчер переходит дальше вверх по фокусной иерархии. Если он доходит до верха, не найдя результата, он возвращает значение null.

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

    window.focus();
    

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

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

    9.2.5. Извещение об изменении

    Одно важное свойство системы команд - это система извещений команд об изменениях. Она называется также системой обновления команд.

    Команды можно создавать и управлять ими, их можно искать и выполнять, но что будет, если команда изменится? Предположим, состояние команды enabled изменит значение с true на false. Как та часть приложения, которая использует команду, узнает, что команда более недоступна? Извещение об изменении решает эту проблему.

    В главе 6, "События", мы рассматривали шаблон проектирования "источник события - наблюдатель". Mozilla использует этот же шаблон для отслеживания изменения состояния команд. Вместо того чтобы один или более тегов XUL наблюдали за атрибутами другого тега XUL, они наблюдают за proxy-функцией конкретной команды. Если состояние команды меняется, диспетчер рассылает извещение об изменении его функторов, а наблюдающие теги XUL получают эти извещения. Даже если функторы помещены внутрь кода контроллера для простоты, извещения об изменении состояния все равно генерируются. Таким образом, удобно считать, что функторы всегда имеют место, поскольку это объясняет появление сообщений.

    Диспетчер пользуется списком наблюдаемых тегов XUL в форме объектов DOM. Это обычные объекты DOM, но они называются объектами, обновляющими команды (command updaters), обновителями команд, когда используются в этом качестве. Диспетчер может добавлять и удалять эти объекты из списка.

    Событие DOM генерируется и передается системе обработки событий с помощью метода dispatchEvent(). Извещение об изменении команды передается системе "источник события-наблюдатель" с помощью метода UpdateCommands() диспетчера или окна. Все наблюдатели, ожидающие некоторого события, получат его. В результате такое событие действует точно так же, как и любое событие, которое может получить XUL-тег.

    События обновления команд работают в XUL, но не в HTML. Далее мы коротко рассмотрим синтаксис и способы использования таких событий.

    9.3. Как вызвать команду

    Команды в Mozilla могут быть вызваны различным образом.

    Если объекты контроллер или функтор доступны, их методы могут быть вызваны прямо из JavaScript. При этом мы игнорируем систему команд и относимся к этим объектам, как к любым другим.

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

    Из JavaScript может быть вызван метод doCommand() XUL-тега, способного иметь фокус. Если это выполнено, тег <command>, указанный как значение атрибута команды, будет выполнен. При этом мы не используем диспетчер или контроллеры.

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

    Если порождено такое событие DOM, метод dispatchEvent() может быть вызван из любого тега XUL. Если тег принадлежит фокусному кольцу, то вызов этого метода эквивалентен пользовательскому вводу, порождающему такое событие. То есть данный случай эквивалентен предыдущему.

    Во всех трех описанных случаях выполняемая команда может обратиться к диспетчеру. Это свяжет тег <command> с контроллером.

    Наконец, если вызван метод диспетчера UpdateCommands(), любое сообщение об изменении команды будет послано всем XUL-тегам, наблюдающим за изменениями. Это означает, что событие commandupdate будет послано всем обновителям команд (command updaters).

    Приложения классической Mozilla включают функцию, называемую goDoCommand(). Эта функция управляет диспетчером. Это стартовая точка команд в таких приложениях. Она хранится в файле globalOverlay.js архива toolkit.jar в директории chrome.

    9.3.1. Круг силы

    Данный краткий обзор системы команд предоставляет удобную возможность программистам прикладных приложений. Реализация команды может и начаться, и кончиться в единственном документе XUL, как было показано ранее в примере "hello, world".

    Команда может войти в систему распространения команд из обычного JavaScript. Этот скрипт может сам по себе быть обработчиком событий, возможно, для более традиционных событий DOM, таких как события click или select. В этом случае скрипт должен быть привязан к тегу XUL.

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

    Наконец, этот тег XUL может получать извещения об изменении состояния команды по системе обновления команд.

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

    9.4. Использование команд в XUL

    Для команд в Mozilla есть специальный тег, <command>. Можно использовать также <commandset> и <commands>, но это определяемые пользователем теги, не имеющие собственного значения. Тег <commandset> используется как любой иной тег-контейнер, например, <keyset>, и содержит набор тегов <command>.

    XUL также имеет набор специальных атрибутов, применимых к любому тегу. Это следующие атрибуты:

    command events targets commandupdater
    

    и два обработчика событий:

    oncommand oncommandupdate
    

    Вне рамок XUL Mozilla также имеет набор заранее определенных имен команд, но мы обсудим это позже.

    9.4.1. Теги <command> и command=

    Тег <command> используется для определения команды, так же как тег <key> используется для определения отклика на нажатие клавиши. Он представляет команду и может воплощать конкретные ее аспекты. В терминах "круга силы" (9.3.3.1) тег <command> представляет и идентификатор, чтобы вызвать команду, и реализацию, выполняющую команду. Он имеет следующие специальные атрибуты:

    disabled oncommand
    

    Атрибут disabled может принимать значение true, в этом случае команда бездействует.

    oncommand принимает значение JavaScript-кода, используемого вместо какого-либо контроллера. Иногда используются атрибуты disabled, label, accesskey, и checked. Их задействует система обновления команд, описываемая ниже, но они не имеют никакого специального значения в теге <command>.

    Пример использования тега <command>:

    <command id="test-command" oncommand="alert('executed');"/>
    

    Атрибут id именует команду. Обработчик события oncommand дает реализацию команды - это обработчик команды. Если данный тег порождает событие DOM с именем command, то этот обработчик запускается. Поскольку тег <command> не имеет графического отображения (виджета), этот тег редко используется для порождения события.

    Атрибут command позволяет другим тегам применять тег <command> таким же способом, каким атрибут key дает возможность другим тегам использовать тег <key>. Например:

    <mytag id="myAppTag" command="test-command"/>
    

    Если тег <mytag> порождает событие command правильного типа, вызывается упомянутая выше функция alert(). Для реализации "mytag" можно использовать только следующие теги:

    <button> <checkbox> <radio> <toolbarbutton> <menu> <menuitem> <key>
    

    Когда один из этих тегов получает фокус, воздействие пользователя порождает событие command автоматически. Обработчик oncommand можно добавить прямо в эти теги, но это нерекомендуемый способ. Лучше реализовать все нужные команды в тегах <command>, чем "размазать" их реализацию по многим виджетам приложения.

    Обработчик oncommand может быть вызван прямо из JavaScript:

    var target = document.getElementById("mytag-id"); 
    target.doCommand();
    

    Если атрибут oncommand определен, не используется ни диспетчер, ни контроллеры. Выполняется лишь код обработчика команды. Код может вызвать диспетчер. В этом случае диспетчер выполняет свою задачу, как обычно.

    Есть еще один способ использования атрибута command. Система связок XBL имеет свой тег <handler>, позволяющий обычному событию DOM, подобно событию click, передаваться в указанную команду. Для этого в XBL-теге <handler> используется атрибут command.

    9.4.2. Атрибуты commandupdater="true" и oncommandupdate=

    Если XUL-тег имеет эти атрибуты, он обладает способностью обновлять команды и будет получать извещения об обновлении команд. Эти извещения - часть системы "источник события - наблюдатель". Когда парсер XUL обнаруживает атрибут commandupdater, он регистрирует тег, имеющий его, как тег-наблюдатель в диспетчере. Когда вызывается метод диспетчера UpdateCommands(), все зарегистрированные наблюдатели получают извещения о событии. В результате, если происходит событие commandupdate, запускается обработчик события oncommandupdate данного тега. Пример такого кода прост:

    <mytag commandupdater="true" oncommandupdate="update_all()"/>
    

    Функция update_all() - некоторый конкретный скрипт данного приложения. Он делает всю необходимую работу с изменившейся командой. Например, такая функция есть в классической Mozilla. Файл globalOverlay.js архива toolkit.jar в chrome содержит функцию goUpdateCommand() (и иные функции go...). Эта функция выполняет стандартные процедуры обновления команд. Она устанавливает атрибут disabled тега <command>, вызывая метод контроллера isCommandEnabled().

    Дальнейшие примеры обновления команд приведены в разделе "Как заставить виджет реагировать на состояние команды". Тегу, способному обновлять состояние команды, можно указать и такой фильтр:

    <mytag commandupdater="true" events="focus blur" 
    targets="*" oncommandupdate="update_all()"/>
    

    Атрибут events ограничивает список событий, генерирующих событие commandupdate. Это может быть список событий, разделенных пробелами или запятыми, и он может включать имена команд. Атрибут targets имеет тот же синтаксис, за исключением того, что вместо списка имен используется список идентификаторов (ids). Делать это за редким исключением не рекомендуется. Таким способом мы ограничиваем список рассматриваемых событий теми, чьи цели (теги) включены в список ids. Значение по умолчанию обоих атрибутов равно "*", то есть "все".

    Даже если атрибут targets указан, событие commandupdate передается тегу, способному изменить состояние команды, но не тегам - целям. Ниже мы увидим, как используется данная функциональность.

    Событие commandupdate можно вызвать из JavaScript, как и событие command. Чтобы это сделать, следует вызвать метод диспетчера UpdateCommands(). В случае команд, определенных в XUL, это не слишком гибкий и полезный подход, потому что простые обработчики событий, используемые в oncommand, не имеют состояний. Только если команда реализуется JavaScript (то есть используется объект controller), вызов события commandupdate имеет смысл.

    9.4.3. Теги <commandset> и <commands>

    Тег <commandset> определяется программистом и не имеет специального значения. Его используют, чтобы сгруппировать команды вместе.

    Иногда приложение XUL состоит из множества отдельных файлов. В этом случае тег <commandset> зачастую имеет id, по которому его можно найти в результирующем документе, тем же способом, который описан в главе 12, "Оверлеи и chrome".

    Он используется и при обновлении команд. Это удобно, поскольку его дочерние теги обычно являются тегами <command>. Когда генерируется событие commandupdate, обработчик oncommandupdate тега <commandset> просматривает по очереди все дочерние теги и проверяет их состояние. Такая стратегия используется в chrome классической Mozilla.

    Наконец, тег <commands> также является тегом, определяемым программистом и не имеющим специального значения. Он используется лишь для группирования тегов <commandset>. Этот тег тоже применяется в системе оверлеев Mozilla. В результате все теги <commandsets>, разбросанные по файлам приложения, окажутся дочерними тегами тега <commands>.

    9.5. Использование команд в AOM

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

    Каждый XUL тег в иерархии документа может быть целью события, поэтому каждый тег поддерживает метод dispatchEvent(). В зависимости от того, какой из элементов DOM вызовет метод dispatchEvent(), могут сработать разные реализации соответствующей команды.

    Объект document в документе XUL имеет свойство commandDispatcher. Это свойство является единственным диспетчером команд. Он имеет метод UpdateCommands() и используется для добавления обновителей команд. Диспетчер команд также предоставляет программисту прикладного приложения интерфейс, позволяющий передвигать фокус документа по виджетам в фокусном кольце.

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

    В HTML это множество пусто для всех тегов, за исключением <textbox>. <textbox> имеет единственный контроллер, поскольку он строится на основе тега HTML <input type="textbox">.

    Свойство window.document не является тегом и не имеет свойства controllers.

    Объект window сам по себе также не является тегом XUL, но он имеет свойство controllers, чье множество содержит единственный контроллер. Этот единственный контроллер называется контроллером фокуса и используется для управления фокусом окна и событиями в фокусном кольце.

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

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

    var functor = { 
      isCommandEnabled        : function (cmd) { ... },
      getCommandStateParams   : function (cmd) { },
      doCommandParams         : function (cmd) { },
      doCommand               : function (cmd) { ... },
    }
    

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

    Далее функторы регистрируются объектом контроллер-команд.

    Если контроллер существует, функтор можно зарегистрировать, используя доступный интерфейс, например:

    controllers.getControllerAt(2).registerCommand(cmd,functor);
    

    На практике существует лишь один контроллер, который можно использовать повторно, и он не является автоматически доступным в AOM (его нужно предварительно создать с помощью XPCOM). Тем не менее, контроллеры можно реализовать полностью на JavaScript. Объекты таких контроллеров должны реализовать интерфейс nsIController. На In JavaScript это выглядит так:

    var controller = {
      supportsCommand    : function (cmd) { ...}
      isCommandEnabled   :  function (cmd) { ...}
      doCommand          :  function (cmd) { ...}
      onEvent            :  function (event) { ...}
    }
    

    Функторы можно использовать, декларировать и даже помещать их реализацию в тело объекта контроллера, примеры уже приводились. Если мы хотим использовать белее структурированный подход, контроллер может реализовать интерфейс nsIControllerContext. В этом случае объекты-функторы могут добавляться и уничтожаться во время выполнения, после инициализации контроллера объектом nsIControllerCommandTable. Этот объект также можно реализовать на JavaScript.

    После того как мы реализовали контроллер, следующей задачей будет добавить его к объекту элемента DOM соответствующего тега.

    aNode.controllers.appendController(controller);
    

    Метод onEvent() выполняется, только если код приложения вызовет его явно. Автоматически он не запускается.

    Набор контроллеров объекта window - самое подходящее место для команд общего назначения.

    В случае окна на чистом XUL, последний этап реализации - инициация фокуса, чтобы диспетчеру было что исследовать, когда вызывается команда. Самый простой способ сделать это - дать фокус окну как целому, используя объект AOM window:

    window.focus()
    

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

    Адресная книга почтового клиента Mozilla - хороший пример работы системы команд. Здесь имеются два контроллера, по одному на каждый тег <tree> в диалоговом окне адресной книги. Начните изучение с файла abCommon.js в архиве messenger.jar .

    9.5.1. Как заставить виджет реагировать на состояние команды

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

    Системы команд и обновления команд отвечают этой цели. Листинг 9.6 показывает фрагмент XUL, демонстрирующий первую половину такой системы.

    <updater commandupdater="true" oncommandupdate="update()"> 
       <command id="paste" oncommand="doPaste()"/> 
    </updater> 
    <button label="Paste1" command="paste" observes="paste"/> 
    <description value="Paste Enabled" observes="paste"/>
    
    Листинг 9.6. Объект controller, не имеющий отдельных функторов.

    Тег <updater> - это определенный программистом тег, который мог бы называться и <commandset> - название тега не столь важно. Теги <button> и <description> представляют собой два места, в которых пользователь видит состояние команды Paste. Так как тег <description> не поддерживает атрибута command, он действует лишь как индикатор состояния в режиме "только чтение", а не как виджет, обрабатывающий ввод пользователя.

    Функция doPaste() реализует операцию Paste. Эта функция может использовать диспетчер, а может и не использовать его, в зависимости от реализации. Функция update() отражает состояние команды в графическом интерфейсе. Она также может быть реализована с помощью или без помощи диспетчера, но правильнее всего заставить ее обращаться к контроллеру или функтору, реализующему команду и получать информацию о состоянии оттуда.

    Вторая половина системы - это JavaScript. Листинг 9.7 демонстрирует функцию update().

    function update() { 
      var cont, node; 
      cont = document.commandDispatcher.getControllerForCommand('paste'); 
      node = document.getElementById('paste'); 
      if ( !cont || !node ) return; 
      if ( cont.isEnabled(cmd) ) { 
        node.removeAttribute("disabled"); 
        node.removeAttribute("value"); 
      } 
      else { 
        node.setAttribute("disabled", true"); 
        node.setAttribute("value", "Paste Disabled"); 
      } 
      if ( cont.clipboardEmpty() ) { 
        node.setAttribute("value", "Nothing to Paste"); 
      } 
    }
    
    Листинг 9.7. Обновитель команды на основе контроллера.

    Эта функция знает только о команде Paste. Она ищет реализацию этой команды и проверяет ее состояние с помощью функций isEnabled() и clipboardEmpty(). isEnabled() - стандартный метод контроллера, но данный контроллер должен иметь добавочный метод clipboardEmpty(). Эта конкретная команда имеет два состояния: доступна ли она и есть ли что- то в буфере обмена. Если в буфере обмена ничего нет, то нечего и вставить в текст.

    На основании анализа этих состояний функция update() загружает тег <command> с необходимыми атрибутами. Эти атрибуты не являются значащими для самого тега <command>. Они могут влиять на графические виджеты. И <button>, и <description> получают эти атрибуты, но для <button> значащим является атрибут disabled, а для <description> - value.

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

    Здесь существуют две системы "источник события - наблюдатель". Первая - это автоматическая регистрация диспетчером тега <updater> как тега, обновляющего команду. Вторая - специфичный для данного приложения атрибут observes, помещенный в двух тегах-виджетах. Вторая система - просто некоторое решение, точно так же можно было бы обновить теги-виджеты и прямо из метода update(), например, используя идентификаторы тегов - ids, либо как-то иначе.

    9.6. Команды и компоненты XPCOM

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

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

    Единственный способ решить эту проблему - подробно охарактеризовать лишь сами интерфейсы. Таблица 9.1. следует версии платформы 1.4. Лишь некоторые из этих интерфейсов в настоящее время приняли завершенный вид, в более поздних версиях они могут немного меняться.

    Таблица 9.1. Компоненты XPCOM, используемые в инфраструктуре системы команд

    Имя интерфейсаСуществующая реализация XPCOMЦель
    nsICommandХотя имя кажется логичным, такого интерфейса нет
    nsICommandHandler@mozilla.org/embedding/browser/nsCommandHandler;1Реализация команд нижнего уровня - не используется в JavaScript
    nsICommandHandlerInit@mozilla.org/embedding/browser/nsCommandHandler;1Используется для инициализации объектов nsICommandHandler
    nsIController@mozilla.org/embedcomp/base-command-controller;1Базовый контроллер, поддерживающий таблицу команд
    nsIControllerContext@mozilla.org/embedcomp/base-command-controller;1Используется для инициализации контроллера
    nsIControllerCommandTable@mozilla.org/embedcomp/controller-command-table;1Изменяемый набор команд
    nsICommandController@mozilla.org/embedcomp/base-command-controller;1Некорректно названный интерфейс, добавляющий базовому контроллеру вызов команды с аргументами
    nsIControllerCommandGroup@mozilla.org/embedcomp/controller-command-group;1Используется для группирования изменяемого множества команд
    nsIControllerCommandNoneБазовый функтор
    nsICommandManager@mozilla.org/embedcomp/command-manager;1Используется для управления вспомогательными методами функтора
    nsICommandParams@mozilla.org/embedcomp/command-manager;1Структура данных, используемая как список параметров при выполнении команды с параметрами
    nsIControllersNoneНабор контроллеров как свойство элемента DOM
    NsIDOMXULCommandDispatcherДиспетчер, не являющийся компонентом XPCOMДиспетчер команд XUL
    nsPICommandUpdaterВнутренний интерфейс, не используемый в приложениях
    nsISelectionControllerНе работает с системой распространения команд
    nsIEditorController@mozilla.org/editor/composercontroller;1 @mozilla.org/editor/editor-controller;1Два контроллера, использующиеся в приложении Mozilla Composer
    nsITransaction@mozilla.org/transaction-manager;1Три интерфейса, полезные, когда контроллер становится слишком сложен. Используйте их для реализации undo/redo, макросов, истории команд, аудита и другой развитой функциональности
    nsITransactionList
    nsITransactionManager

    Существуют также несколько интерфейсов для реализации синтаксиса командной строки. Их можно использовать для запуска платформы, но они не имеют дела с системой распространения команд.

    9.7. Существующие команды

    До сих пор мы рассматривали аспекты XUL, AOM и XPCOM системы распространения команд платформы Mozilla. Mozilla, однако, - не только платформа, но и законченное приложение (классическая Mozilla). Некоторые команды приложения можно применять и в платформе. Иногда программисты приложений используют это свойство платформы. Простой пример повторного использования существующей технологии - контроллер фокуса. Этот контроллер всегда доступен в окне XUL. Диспетчер команд задействует методы advanceFocus() и rewindFocus(), обеспечивающие управление фокусом, которое обычно пользователь осуществляет с помощью клавиши Tab. Названные методы посылают команды контроллеру фокуса. Этот контроллер можно использовать и для обновления команд. Тег, который должен быть обновлен, может получать извещение об обновлении команды, как результат некоторого события, произошедшего с фокусом. Почему контроллер посылает два события commandupdate на каждый шаг навигации по кольцу фокуса, мы объясним позже.

    Другая разновидность повторного использования кода - заимствование. [theft - дословно "кража", но о какой краже может идти речь, если мы рассматриваем GPL-код? - прим. пер.] Файл globalOverlay.js в архиве toolkit.jar в chrome содержит полезные функции, позволяющие манипулировать командами и контроллерами. Архитектуру команд, использованную в адресной книге почтового клиента Mozilla и в системе контроллеров приложения Composer, можно применять в качестве образца. Некоторые из связок клавиатура-команда реализованы достаточно компактно, чтобы можно было использовать содержащие их файлы оверлеев.

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

    Набор приложений Mozilla Composer, Messenger и Navigator содержит около сотни команд. Одного взгляда достаточно, чтобы понять, что даже простейшее пользовательское действие должно быть воплощено в ту или иную команду, поскольку это действие влечет за собой изменение интерфейса. Это значит, что все действия, от незначительных (например, "Select Current Word"), средней значимости (например, "Delete Message") и до очень важных (например, "Load URL") должны иметь реализацию в наборе приложений Mozilla. Многие из этих команд реализованы на C++, но некоторые доступны непосредственно на JavaScript в chrome.

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

    Полезно ознакомиться с кодом команд классической Mozilla, доступным в файле ComposerCommands.js в архиве comm.jar в chrome.

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

    Последняя разновидность повторного использования связана с небольшой, но важной областью - XUL-тегом <textbox>. Операции с ним реализованы в командах браузера Mozilla. Если даже все остальные команды Mozilla не использовать в приложении, по крайней мере, эти команды всегда помогут корректно организовать работу с элементами формы.

    9.8. Стилевые опции

    Система команд Mozilla не имеет стилей

    9.9. Практика: конструируем команды

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

    1. Спланировать команды.
    2. Реализовать команды.
    3. Инсталлировать реализацию команд.
    4. Вызывать команды, когда требуется выполнение.

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

    Какие выбрать имена команд - вопрос. Когда мы используем систему команд, мы пользуемся общим для всей платформы пространством имен. В диалоговом окне Edit это не столь важно, потому что здесь нет прикладного кода другого приложения. В главном же окне браузера панель инструментов NoteTaker и элементы меню Tools могут иметь общие команды. Имена команд, которые мы создадим, не должны конфликтовать с уже существующими.

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

    В предыдущей главе мы разработали четыре возможных аргумента функции action():

    edit keywords cancel save
    

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

    1. Открыть диалоговое окно. Вызывается элементом меню Tools и кнопкой Edit на панели инструментов.
    2. Закрыть диалоговое окно. Вызывается кнопкой Cancel и кнопкой Save диалогового окна.
    3. Загрузить текущую запись. Вызывается обработчиком onload диалогового окна Edit.
    4. Сохранить текущую запись. Вызывается кнопками Save на панели инструментов и в диалоговом окне Edit.
    5. Удалить текущую запись. Вызывается кнопкой Delete на панели инструментов.
    6. Переключиться во вкладку Edit. Вызывается комбинацией клавиш в диалоговом окне Edit.
    7. Переключиться во вкладку Keywords. Вызывается комбинацией клавиш в диалоговом окне Edit.

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

    notetaker-open-dialog notetaker-close-dialog notetaker-load 
    notetaker-save notetaker-delete notetaker-nav-edit 
    notetaker-nav-keywords
    

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

    Для реализации команд мы можем использовать тег <command> и атрибут command=, либо можем создать контроллер команд на одном из объектов DOM, или сочетать оба подхода. Выберем для начала командный контроллер, поскольку это прояснит материал главы и так будет удобней продемонстрировать процесс отладки.

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

    Наша функция action() хорошо нам служила, и незачем от нее отказываться. Мы создадим объекты контроллера, вызывающие метод action() в момент вызова команды. На языке конструирования модели: функторы команды агрегированы в объектах контроллера, делегирующих выполнение команды функции action(). Листинг 9.8 демонстрирует необходимый нам контроллер.

    var notetaker = { 
      _cmds : { 
        "notetaker-open-dialog":true, 
        "notetaker-close-dialog":true, 
        "notetaker-load":true, 
        "notetaker-save":true, 
        "notetaker-delete":true, 
        "notetaker-nav-edit":true, 
        "notetaker-nav-keywords":true 
      }, 
      supportsCommand : function (cmd) { return (cmd in this._cmds); }, 
      isCommandEnabled : function (cmd) { return true; }, 
      doCommand : function (cmd) { return action(cmd);}, 
      onEvent : function (cmd) {} 
    };
    
    Листинг 9.8. Контроллер команд NoteTaker

    По сути, этот объект - всего лишь обертка функции action(), удовлетворяющая инфраструктуре системы команд платформы. Она столь проста, потому что (1) команды всегда доступны (enabled) и (2) команды нельзя добавлять динамически. Второй, созданный "шутки ради", контроллер приведен в листинге 9.9.

    var detective = { 
      supportsCommand : function (cmd) { return true; }, 
      isCommandEnabled : function (cmd) { return true; }, 
      doCommand : function (cmd) {
        throw("NoteTaker  detective called on command: " + cmd);
        return true; 
      }. 
      onEvent : function (cmd) {}
    };
    
    Листинг 9.9. Контроллер-детектив

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

    Наконец, контроллеры нужно установить в каком-либо объекте DOM. Мы выберем для установки объект DOM тега <window> (объект AOM window). Мы можем установить контроллер на одном из двух окон: главном окне браузера (для панели инструментов и элемента меню Tools), и диалоговом окне Edit. Мы установим оба контроллера, notetaker и detective, в обоих окнах, т.е. появятся четыре контроллера. Оба окна получат поддержку всех семи команд, но каждое окно будет обрабатывать лишь свое подмножество команд. Вот простая функция, устанавливающая контроллеры в одно окно.

    function install_controllers() 
    { 
    window.controllers.appendController(notetaker); 
    window.controllers.appendController(detective); 
    }
    

    Поскольку контроллер detective добавляется вторым, он может уловить лишь то, что пропустит первый контроллер, notetaker. Оба контроллера, функцию install_controllers() и ее вызов можно поместить в отдельный файл JavaScript. Если мы затем включим тег-инструкцию <script src="controllers.js"> во все необходимые окна, оба окна получат отдельные, но идентичные объекты controller.

    Вызвать команды также несложно. Простая функция предоставляет необходимую логику:

    function execute(cmd) { 
    window.focus(); 
      try { 
        var disp = window.document.commandDispatcher; 
        var ctrl = disp.getControllerForCommand(cmd); 
        ctrl.doCommand(cmd); 
    } 
    catch (e) { 
      throw (" Command Unknown: " + cmd + ". Error: " + e); 
    } 
    window.focus(); 
    }
    

    Функция execute() генерирует ошибку, если диспетчер не может найти подходящий контроллер. Вызовы window.focus() до и после диспетчера очень важны. Благодаря им мы гарантируем, что диспетчеру будет с чем работать, и что окно останется корректным после работы диспетчера.

    В окне браузера мы можем вместо данной функции использовать уже существующую, goDoCommand(). Эта функция находится в файле globalOverlay.js архива toolkit.jar в chrome. Она почти такая же, как execute(), но допускает вызов неизвестной команды. В нашем случае мы выбираем сообщение об ошибке, а не молчание в ответ на появление неизвестной команды. Файл globalOverlay.js содержит много небольших полезных функций, используемых в классической Mozilla.

    Функцию execute() помещают везде, где может потребоваться соответствующая команда. Мы заменим все вызовы функции action() функцией execute() в диалоговом окне. Таким образом теги <key> изменятся следующим образом:

    <key key="e" oncommand="action('edit')"/> 
    <key key="e" oncommand="execute('notetaker-nav-edit')"/>
    

    Ранее мы не разработали ничего для обработки кнопок в диалоговом окне. Теперь мы можем, по крайней мере, вызвать правильную команду. Мы можем вызвать обработчик события onclick для каждой кнопки, либо послать событие button тегу <command>. Выберем последнее. Кнопка Cancel изменится с:

    <button label="Cancel" accesskey="C"/>
    

    на

    <button label="Cancel" accesskey="C" command="dialog.cmd.cancel"/>
    

    Атрибут command= требует соответствующего тега <command>, который выглядит следующим образом:

    <command id="dialog.cmd.cancel" oncommand="execute
     ('notetaker-close-dialog')"/>
    

    Вот как это работает: пользователь, нажимая на кнопку, порождает события, одно из которых - "команда", событие DOM 2. Тег <command> ожидает это событие - результат атрибута command тега <button>. Возникающее событие принимает тег <command> - стартует его обработчик oncommand. На этом влияние события заканчивается. Стартующий обработчик oncommand вызывает execute(), а тот посылает поименованную команду контроллеру, который диспетчер найдет для него.

    Теги <command> для кнопок Save и Cancel выглядят следующим образом:

    <commandset> 
    <command id="dialog.cmd.save" oncommand="execute('notetaker-save');
      execute('notetaker-closedialog');"/>
    <command id="dialog.cmd.cancel" oncommand="execute
      ('notetaker-close-dialog');"/> 
    </commandset>
    

    К кнопке Save привязаны две команды. Мы можем объединить их в одну, но стоить отметить, что возможность запустить любое количество команд одновременно существует. Используя теги <command> и <key>, мы можем сгруппировать все обработчики событий диалогового окна на вершине документа XUL. Мы можем сделать в точности то же самое и для кнопок панели инструментов, с тем отличием, что потребуется три тега <command> вместо двух.

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

    Для диалогового окна Edit нам нужно изменить логику функции action() так, чтобы учесть новые имена команд. Для панели инструментов мы пока не знаем, как именно изменить функцию. Так что оставим ее просто:

    function action(cmd) {};
    

    На этом наши эксперименты с командами пока заканчиваются.

    9.10. Отладка: обработка неопознанной команды

    Когда взаимодействуют система команд и система управления фокусом Mozilla, нужно учитывать некоторые тонкости. Очень важно, чтобы фокус был корректно установлен, когда впервые появляется окно XUL. Так вызываются window.focus() или window.content.focus().В последних версиях нет жесткой необходимости следить за фокусом, но это по-прежнему хороший стиль программирования.

    Если проблемы с фокусом все-таки появятся, есть опасность нарушить работу не только платформы Mozilla, ни и самого окружения рабочего стола. Завершить работу платформы не всегда достаточно, может потребоваться даже перезагрузка компьютера, по крайней мере, в Microsoft Windows.

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

    Существуют многочисленные примеры кода в платформе Mozilla и ее приложениях, где не была реализована необходимая проверка данных на корректность. Обычный пример - атрибут XML, имеющий булевские значения. Обычно платформа проверяет их на значение "true", и это значит, что в ином случае они могут иметь значение false. Это быстрый стиль разработки, но он же делает отладку более трудной, потому что невалидные значения или принимаются или молча игнорируются системой. Это в особенности верно для команд, которые имеют добавочный уровень сложности - диспетчер, между вызовом команды и ее выполнением.

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

    Строгая проверка строк команд не реализована в платформе Mozilla, потому что приложения должны быть открыты для улучшений. Для локальных приложений нет оснований пренебрегать точным определением и проверкой команд. Лишь в редких случаях, подобных расширяемым играм типа MUDs [Multi-User Dungeons--text games], IRC, онлайновых игр - можно оставить множество команд расширяемым и точно не определенным.

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

    Заметим, что неизвестные команды и неизвестные события - разные вещи. Нет необходимости блокировать или улавливать неизвестные события. Такие события будут обработаны службой обнаружения событий, см. 6.6. "Отладка: обнаружение событий", обсуждавшейся в главе 6, "События".

    9.11. Итоги

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

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

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

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


    об авторе

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





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