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

Команды

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) {};

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

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