Опубликован: 10.12.2007 | Уровень: специалист | Доступ: платный
Лекция 9:

Команды

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, либо как-то иначе.

Дмитрий Гуменюк
Дмитрий Гуменюк
Россия, Звенигород
Konstantin Grishko
Konstantin Grishko
Россия, Москва, Московский финансово-промышленный университет "Синергия", Москва