В этой лекции рассматривается
Очень важно, чтобы задачи, выполняемые окном приложения, как-то отделялись от своих пользовательских интерфейсов. С одной стороны, это связано с тем, что XUL-интерфейсы легко поддаются изменениям, и во время проектирования, и во время работы приложения. Такие интерфейсы накладывают жесткое ограничение на архитектуру приложения. С другой стороны, многие существующие методики разработки программного обеспечения могут применяться к задачам приложения, если их можно выразить самостоятельно. Примеры таких методик - варианты использования и схемы иерархий. Отдельное определение задачи делает ее повторное использование более вероятным. В конце концов, такая стратегия разделения образует гибкую среду проектирования.
У профессионально созданного приложения все команды будут формально определены во время проектирования. Определение команд, которые нужно реализовать - часть процесса аналитического проектирования и может использоваться для поиска запутанных, дублирующих и беспорядочных действий. Такие проблемы проектирования в этом случае можно будет решить до этапа реализации, а не после.
Традиционные приложения на основе HTML часто состоят из самой запутанной части JavaScript-кода. Систему команд также можно рассматривать как попытку избавиться от этого неструктурированного подхода.
Иллюстрация в начале этой лекции демонстрирует те части Mozilla,
которые реализуют команды. Из рисунка видно, что [m]<key>[/m]. [m]<key>[/m], потому что у нее нет никакого
пользовательского интерфейса.
Найти команду в Mozilla очень легко. Простые операции вроде сохранения файла, добавления закладки, выбора содержимого, выделения полужирным шрифтом, прокрутки страницы или отмены - все это команды.
Обнаружить команду в терминах исходного кода не так легко. Самый
очевидный признак команды - простая строка, чье имя - имя команды.
Такие имена могут определяться платформой или программистом
приложения. К сожалению, остальная часть инфраструктуры команд
довольно сильно разбросана по платформе. Части
Простой пример использования
<?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>
Тег <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>
Этот код описывает объект control, реализующий команду; фактически,
данный объект может реализовать несколько команд. Команда реализована
в методе doCommand(). Затем объект нужно встроить в инфраструктуру
команд платформы. Он будет вызываться каждый раз, когда потребуется
выполнить данную команду. И, наконец, команда может быть вызвана в
любом месте документа XUL с помощью функции execute(). В данном случае
удобно использовать обработчик onclick.
Очевидно, второй пример сложнее первого, но есть серьезные основания считать этот подход лучшим.
Система распространения очень не похожа на традиционную клиент-
серверную архитектуру. В такой архитектуре серверы выполняют команды
независимо и удаленно от клиентского GUI. Зачастую они выполняются без
видимого эффекта. Появляется лишь сообщение об их выполнении.
Например, HTTP запросы GET и POST игнорируют графическую оболочку
клиентского браузера, они лишь возвращают код статуса, и, может быть,
новый документ.
Команды Mozilla не похожи на клиент-серверные команды. Они тесно
связаны с клиентской графической оболочкой. Примером может служить
операция Bold в компоновщике Mozilla (да и в любом текстовом
процессоре). Bold применяет операцию стиля на выделенном фрагменте,
который обычно виден на дисплее.
Последний абзац означает, что команды не "удалены" от
прикладного программиста. Они часть того кода, который он создает и
подключает к
Mozilla использует специальный дизайн кода, называемый Command
, "образец команды", чтобы отделить имена команд от
их реализации. Большинство современных пакетов разработки GUI включают
технологии, подобные Command . Ниже будет показано, как
работают такие образцы.
// 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
Объект не только кажется необоснованно сложным по сравнению
с простой функцией, но он вдобавок требует лишней глобальной
переменной для своей работы. Однако он гораздо более гибок, поскольку
имеет стандартизованный интерфейс. Все -объекты имеют
единственный метод , выполняющий реализованную функтором команду
и возвращающий лишь сообщение об ошибке или об успешном выполнении.
Таким образом, все
Объект enabled.
На практике большинство систем, реализующих команды, включая Mozilla,
в явном виде поддерживают состояние enabled. Прикладной код может
опрашивать это состояние, чтобы узнать, доступна ли команда, и затем
реагировать соответствующим образом. Объект может хранить
любую информацию состояния о команде, какая представляется удобной, а
не только состояние enabled. Другими состояниями могут быть имя
команды, уникальный id, флаги, означающие, готова ли команда к
выполнению, блокирована или оптимизирована; язык, на котором команда
реализована, или ее версия и т.д.
В данном примере объект модифицирует глобальную переменную
num при выполнении команды. Это позволяет методу не иметь
параметров, что делает все объекты в этом отношении
одинаковыми. num может быть свойством объекта , но это
неудачное решение. Это плохо, потому что состояние enabled ), реализация команды не использует информацию о
состоянии при выполнении. Информация о состоянии используется кодом,
который вызывает
так:
exec: function () { return really_halve_it(); }
В этом варианте функция really_halve_it() выполняет всю реальную
работу за команду и может быть реализована где-нибудь в другом месте,
например, в библиотеке.
В последнем примере объекта подчеркивается, что он не
содержит реализации команды. Это всего лишь точка доступа к команде и
ее состоянию. Это
В литературе о функторах, не связанной с Mozilla, метод
часто обозначают как execute(). В 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");
Объект controller содержит объект _cmds, устанавливающий
соответствие между именами свойств и supportsCommand() сообщает, знает ли
контроллер о команде; doCommand() выполняет onEvent() передает
Последний метод - isCommandEnabled() в данном случае является
некоторым жульничеством. Он должен проверять свойство enabled у
соответствующего true.
Это и потому также так, что ни один из приведенных enabled.
Кажется, что они должны реализовать три полноценных
Этот последний пункт важен. Поскольку контроллер полностью скрывает
реализацию команды (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");
В данном примере контроллер действует, как если бы все три
Сложные контроллеры также могут многое выиграть от явной реализации
команды. Иногда возникают проблемы с синхронизацией, которые лучше
всего решить в контроллере. Пример - реализация команды в
редакторах, подобных Classic . Контроллер отвечает за
реализацию конкретной команды, так что его логично использовать и для
сохранения controlling ).
Контроллеры также удобны для размещения в них макро-языков, механизмов
синхронизации, и всего, что связано с существованием команд.
Mozilla не ограничивается единственным статическим контроллером. Можно создать столько контроллеров, сколько вам требуется. Так сделано, например, в компоновщике Mozilla, где имеются три контроллера, каждый из которых реализует свой набор команд.
Все контроллеры должны быть помещены туда, где платформа сможет их найти. Их можно поместить в несколько разных мест.
Контроллеры можно поместить в объект window.
Несколько XUL-тегов являются подходящими местами. Можно
использовать <button>, <, <, <toolbarbutton>, <menu>, <menuitem>, и <key>.
XUL-тег <command> подобен
Использование каждого из этих мест будет рассматриваться позднее, при обсуждении синтаксиса команд.
Если в каком-то месте имеется более одного контроллера, то набор контроллеров называется цепочкой контроллеров. Такое множество контроллеров упорядоченно, и при необходимости они просматриваются по очереди. Это значит, что первый контроллер в цепочке, который может выполнить команду, и будет тем контроллером, который ее выполнит.
Каждый контроллер содержит набор команд; в одном месте может быть ноль или более контроллеров; диспетчер контроллеров работает с набором таких мест. К счастью, это все. Задачей диспетчера контроллеров является поиск и выполнение конкретной команды.
Mozilla имеет один диспетчер для HTML-документов и один - для
документов XUL. Диспетчер HTML невидим и недоступен скриптам ни при
каких обстоятельствах. С диспетчером команд XUL можно работать. Каждое
окно XUL-приложения имеет объект в корне
Диспетчер имеет метод getControllerForCommand(). Данный метод
получает имя команды в виде строки и возвращает контроллер, способный
выполнить эту команду. Диспетчером и возвращаемым контроллером нужно
явным образом управлять из скрипта - они ничего не делают
автоматически. В файлах goDoCommand(). Эта функция
доступна в каждом окне классической Mozilla и применяется чаще
всего.
Диспетчер использует информацию о состоянии текущего фокуса, чтобы
решить, какой контроллер нужен для данной команды. Эта информация о
состоянии включает состояние фокуса окна, все фокусы тегов <iframe>, и любой элемент DOM в фокусном кольце, если он имеет в
настоящее время фокус. Другими словами, диспетчеру нужна информация об
иерархии фокуса. Диспетчер начинает с наиболее глубокого,
специфичного положения фокуса (обычно это элемент формы или меню), и
переходит по дереву DOM вверх вплоть до верхнего окна. На каждом
элементе DOM, способном получить фокус, он исследует цепочку
контроллеров в Command(). Первый же объект controller, реализующий выполнение нужной
команды, возвращается. Если на данном уровне результата не находится,
диспетчер переходит дальше вверх по фокусной иерархии. Если он доходит
до верха, не найдя результата, он возвращает значение null.
Окно XUL и его контент не полностью инициализируют фокус при создании окна. Даже если окно является текущим окном экрана, оно может не иметь фокуса. Это значит, что имеющиеся контроллеры не всегда автоматически доступны, даже если они находятся в вершине текущего окна. Программист должен явно дать окну фокус, если контроллер этого окна должен быть доступен. Простейший способ сделать это - одна строка скрипта:
window.focus();
Важно, чтобы фокус был установлен и до, и после выполнения команды. Если этого не сделать, кольцо фокуса может быть разрушено, и рано или поздно могут возникнуть проблемы. Это ограничение было исправлено в версии платформы 1.4, но явное указание фокуса все еще рекомендуется. Диспетчер не просматривает все подряд в поисках реализации команды.
Если виджет, имеющий фокус, имеет атрибут command, диспетчер не
принимает его во внимание. Он исследует только контроллеры.
Одно важное свойство
Команды можно создавать и управлять ими, их можно искать и
выполнять, но что будет, если команда изменится? Предположим,
состояние команды enabled изменит значение с true на false. Как та
часть приложения, которая использует команду, узнает, что команда
более недоступна? Извещение об изменении решает эту проблему.
В лекции 6, "События", мы рассматривали шаблон
проектирования "
Диспетчер пользуется списком наблюдаемых тегов XUL в форме объектов
DOM. Это обычные объекты DOM, но они называются объектами,
обновляющими команды ( command updaters ), обновителями команд, когда
используются в этом качестве. Диспетчер может добавлять и удалять эти
объекты из списка.
Событие DOM генерируется и передается системе обработки событий с
помощью метода dispatchEvent(). Извещение об изменении команды
передается системе "UpdateCommands() диспетчера или окна. Все наблюдатели,
ожидающие некоторого события, получат его. В результате такое событие
действует точно так же, как и любое событие, которое может получить
XUL-тег.
События обновления команд работают в XUL, но не в HTML. Далее мы коротко рассмотрим синтаксис и способы использования таких событий.
Команды в Mozilla могут быть вызваны различным образом.
Если объекты контроллер или
Из JavaScript может быть вызван метод диспетчера getControllerForCommand(). Этот метод - первый шаг по превращению
имени команды в выполняемый код. В этом случае диспетчер ищет
необходимую нам реализацию команды.
Из JavaScript может быть вызван метод doCommand() XUL-тега,
способного иметь фокус. Если это выполнено, тег <command>,
указанный как значение атрибута команды, будет выполнен. При этом мы
не используем диспетчер или контроллеры.
Если нажата клавиша, имеющая соответствующий тег <key>, или
мы щелкнули мышью на теге, который может иметь фокус, будет выполнена
команда, указанная как значение атрибута command. Это тот же случай,
как вызов метода doCommand(), за исключение того, что тег или клавиша
сначала порождают событие command. Платформа имеет специальный
механизм для определения
Если порождено такое событие DOM, метод dispatchEvent() может быть
вызван из любого тега XUL. Если тег принадлежит фокусному кольцу, то
вызов этого метода эквивалентен пользовательскому вводу, порождающему
такое событие. То есть данный случай эквивалентен предыдущему.
Во всех трех описанных случаях выполняемая команда может обратиться
к диспетчеру. Это свяжет тег <command> с контроллером.
Наконец, если вызван метод диспетчера UpdateCommands(), любое
сообщение об изменении команды будет послано всем XUL-тегам,
наблюдающим за изменениями. Это означает, что событие commandupdate
будет послано всем обновителям команд ( command updaters ).
Приложения классической Mozilla включают функцию, называемую goDoCommand(). Эта функция управляет диспетчером. Это стартовая точка
команд в таких приложениях. Она хранится в файле globalOverlay.js
архива
Данный краткий обзор
Команда может войти в систему распространения команд из обычного
JavaScript. Этот скрипт может сам по себе быть обработчиком событий,
возможно, для более традиционных событий DOM, таких как события click
или select. В этом случае скрипт должен быть привязан к тегу XUL.
Команда может выйти из системы распространения команд и в обычном
JavaScript, реализующем команду. Можно написать
Наконец, этот тег XUL может получать извещения об изменении состояния команды по системе обновления команд.
Все вместе это означает, что команда может начаться и кончиться в
теге XUL (или XUL и JavaScript). Например, тег <toolbarbutton>,
порождающий команду, может получить сообщение, что состояние команды
изменилось. Этот же тег <toolbarbutton> может содержать и
реализацию контроллера команды. Диспетчер и стандартный механизм
обработки событий - лишь кусочки головоломки, созданной платформой
Mozilla.
Для команд в Mozilla есть специальный тег, <command>. Можно
использовать также <commandset> и <commands>, но это
определяемые пользователем теги, не имеющие собственного значения. Тег <commandset> используется как любой иной тег-контейнер,
например, <keyset>, и содержит набор тегов <command>.
XUL также имеет набор специальных атрибутов, применимых к любому тегу. Это следующие атрибуты:
command events targets commandupdater
и два обработчика событий:
oncommand oncommandupdate
Вне рамок XUL Mozilla также имеет набор заранее определенных имен команд, но мы обсудим это позже.
Тег <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 имеет свой тег <, позволяющий обычному событию DOM,
подобно событию click, передаваться в указанную команду. Для этого в
XBL-теге < используется атрибут command.
Если XUL-тег имеет эти атрибуты, он обладает способностью обновлять
команды и будет получать извещения об обновлении команд. Эти извещения
- часть системы "UpdateCommands(), все зарегистрированные наблюдатели
получают извещения о событии. В результате, если происходит событие commandupdate, запускается обработчик события oncommandupdate данного
тега. Пример такого кода прост:
<mytag commandupdater="true" oncommandupdate="update_all()"/>
Функция update_all() - некоторый конкретный скрипт данного
приложения. Он делает всю необходимую работу с изменившейся командой.
Например, такая функция есть в классической Mozilla. Файл
globalOverlay.js архива goUpdateCommand() (и иные функции go...). Эта функция выполняет
стандартные процедуры обновления команд. Она устанавливает атрибут disabled тега <command>, вызывая метод контроллера isCommandEnabled().
Дальнейшие примеры обновления команд приведены в разделе "Как заставить виджет реагировать на состояние команды". Тегу, способному обновлять состояние команды, можно указать и такой фильтр:
<mytag commandupdater="true" events="focus blur" targets="*" oncommandupdate="update_all()"/>
Атрибут events ограничивает commandupdate. Это может быть targets имеет
тот же синтаксис, за исключением того, что вместо списка имен
используется список идентификаторов ( ). Делать это за редким
исключением не рекомендуется. Таким способом мы ограничиваем список
рассматриваемых событий теми, чьи цели (теги) включены в список .
Значение по умолчанию обоих атрибутов равно "*", то есть
"все".
Даже если атрибут targets указан, событие commandupdate передается
тегу, способному изменить состояние команды, но не тегам - целям. Ниже
мы увидим, как используется данная функциональность.
Событие commandupdate можно вызвать из JavaScript, как и событие command. Чтобы это сделать, следует вызвать метод диспетчера UpdateCommands(). В случае команд, определенных в XUL, это не слишком
гибкий и полезный подход, потому что простые обработчики событий,
используемые в oncommand, не имеют состояний. Только если команда
реализуется JavaScript (то есть используется объект controller ), вызов
события commandupdate имеет смысл.
Тег <commandset> определяется программистом и не имеет
специального значения. Его используют, чтобы сгруппировать команды
вместе.
Иногда приложение XUL состоит из множества отдельных файлов. В этом
случае тег <commandset> зачастую имеет id, по которому его можно
найти в результирующем документе, тем же способом, который описан в
лекции 12, "Оверлеи и
Он используется и при обновлении команд. Это удобно, поскольку его
дочерние теги обычно являются тегами <command>. Когда
генерируется событие commandupdate, обработчик oncommandupdate тега <commandset> просматривает по очереди все дочерние теги и
проверяет их состояние. Такая стратегия используется в
Наконец, тег <commands> также является тегом, определяемым
программистом и не имеющим специального значения. Он используется лишь
для группирования тегов <commandset>. Этот тег тоже применяется
в системе оверлеев Mozilla. В результате все теги <commandsets>,
разбросанные по файлам приложения, окажутся дочерними тегами тега <commands>.
Прямое использование 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, чье множество содержит единственный контроллер.
Этот единственный контроллер называется контроллером фокуса и
используется для управления фокусом окна и событиями в фокусном
кольце.
Разработчик прикладных программ наиболее заинтересован в
конкретных, специфичных для данного приложения контроллерах. По
умолчанию таких контроллеров нет. Даже после создания они достижимы
только из
nsIControllerCommand, который выглядит следующим образом, если его
реализовать на JavaScript:
var functor = {
isCommandEnabled : function (cmd) { ... },
getCommandStateParams : function (cmd) { },
doCommandParams : function (cmd) { },
doCommand : function (cmd) { ... },
}
Обычно
Далее
Если контроллер существует,
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, последний этап реализации - window:
window.focus()
Если этого не сделать, фокус должен установить какой-либо иной код, либо сам пользователь, до того, как некоторая команда будет вызвана.
<tree> в диалоговом окне адресной книги. Начните изучение с
файла abCommon.js в архиве
Одна из главных целей , Copy, и Paste, должно иметь операцию Paste в виде,
означающем недоступность, если пока еще ничего не вырезано или не
скопировано в буфер. Любая информация о состоянии, отражающая эти
изменения, должна храниться в команде, реализующей операцию Paste, а
не в самом виджете. Потому что операция Paste может быть доступна
пользователю из нескольких разных виджетов, например меню или панели
инструментов.
<updater commandupdater="true" oncommandupdate="update()"> <command id="paste" oncommand="doPaste()"/> </updater> <button label="Paste1" command="paste" observes="paste"/> <description value="Paste Enabled" observes="paste"/>
Тег <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");
}
}
Эта функция знает только о команде Paste. Она ищет реализацию этой
команды и проверяет ее состояние с помощью функций isEnabled() и clipboardEmpty(). isEnabled() - стандартный метод контроллера, но
данный контроллер должен иметь добавочный метод clipboardEmpty(). Эта
конкретная команда имеет два состояния: доступна ли она и есть ли что-
то в буфере обмена. Если в буфере обмена ничего нет, то нечего и
вставить в текст.
На основании анализа этих состояний функция update() загружает тег <command> с необходимыми атрибутами. Эти атрибуты не являются
значащими для самого тега <command>. Они могут влиять на
графические виджеты. И <button>, и <description> получают
эти атрибуты, но для <button> значащим является атрибут disabled, а для <description> - value.
В результате теги виджетов будут обновлены, и их внешний вид
изменится. Все это происходит в результате единственного вызова метода UpdateCommands() диспетчера команд, начинающего процесс извещения об
изменении/изменениях.
Здесь существуют две системы "<updater> как тега, обновляющего команду. Вторая -
специфичный для данного приложения атрибут update(),
например, используя идентификаторы тегов - , либо как-то иначе.
Хотя
Не следует рассматривать объекты и интерфейсы XPCOM применительно к таким модификациям и улучшениям. Политика именования объектов в данном случае не помогает, слишком мало ключевых слов. В результате разные интерфейсы по названию становятся очень похожими.
Единственный способ решить эту проблему - подробно охарактеризовать лишь сами интерфейсы. Таблица 9.1. следует версии платформы 1.4. Лишь некоторые из этих интерфейсов в настоящее время приняли завершенный вид, в более поздних версиях они могут немного меняться.
| Имя интерфейса | Существующая реализация XPCOM | Цель |
|---|---|---|
nsICommand |
Хотя имя кажется логичным, такого интерфейса нет | |
nsICommandHandler |
@mozilla.org/ |
Реализация команд нижнего уровня - не используется в JavaScript |
nsICommandHandlerInit |
@mozilla.org/ |
Используется для 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 | Используется для группирования изменяемого множества команд |
nsIControllerCommand |
None | Базовый |
nsICommandManager |
@mozilla.org/embedcomp/command-manager;1 | Используется для управления вспомогательными методами |
nsICommandParams |
@mozilla.org/embedcomp/command-manager;1 | Структура данных, используемая как список параметров при выполнении команды с параметрами |
nsIControllers |
None | Набор контроллеров как свойство элемента DOM |
NsIDOMXULCommandDispatcher |
Диспетчер, не являющийся компонентом XPCOM | Диспетчер команд XUL |
nsPICommandUpdater |
Внутренний интерфейс, не используемый в приложениях | |
nsISelectionController |
Не работает с системой распространения команд | |
nsIEditorController |
@mozilla.org/editor/composercontroller;1 @mozilla.org/editor/editor-controller;1 | Два контроллера, использующиеся в приложении Mozilla |
nsITransaction |
@mozilla.org/ |
Три интерфейса, полезные, когда контроллер становится слишком сложен. Используйте их для реализации , макросов, |
nsITransactionList |
||
nsITransactionManager |
Существуют также несколько интерфейсов для реализации синтаксиса командной строки. Их можно использовать для запуска платформы, но они не имеют дела с системой распространения команд.
До сих пор мы рассматривали аспекты XUL, AOM и XPCOM системы
распространения команд платформы Mozilla. Mozilla, однако, - не только
платформа, но и законченное приложение (классическая Mozilla).
Некоторые команды приложения можно применять и в платформе. Иногда
программисты приложений используют это свойство платформы. Простой
пример повторного использования существующей технологии - контроллер
фокуса. Этот контроллер всегда доступен в окне XUL. Диспетчер команд
задействует методы advanceFocus() и rewindFocus(), обеспечивающие
управление фокусом, которое обычно пользователь осуществляет с помощью
клавиши Tab. Названные методы посылают команды контроллеру фокуса.
Этот контроллер можно использовать и для обновления команд. Тег,
который должен быть обновлен, может получать извещение об обновлении
команды, как результат некоторого события, произошедшего с фокусом.
Почему контроллер посылает два события commandupdate на каждый шаг
навигации по кольцу фокуса, мы объясним позже.
Другая разновидность повторного использования кода - заимствование.
[theft - дословно "
Сверх этого повторное использование кода команд Mozilla
затруднительно. Но есть один специальный случай эффективного
использования существующей
Набор приложений Mozilla
Таким образом, если ваше приложение подобно классической Mozilla
(например, еще одна вариация браузера), то эти команды могут
эффективно использоваться. Если предстоит глубокая перестройка
существующих приложений Mozilla, то знакомство с их системами команд
очень существенно. Если ваше приложение -
Полезно ознакомиться с кодом команд классической Mozilla, доступным
в файле ComposerCommands.js в архиве
Не стоит слишком полагаться на возможности повторного использования существующего кода. В конце концов, успех нового приложения зависит как раз от его новизны, а не от того, насколько удачно он имитирует что-то уже хорошо известное. Если ваше приложение работает в точности как уже существующее, то какой смысл его разрабатывать? Большинство новых приложений должно содержать как минимум несколько новых команд. Если вы сможете повторно использовать часть кода на этом пути, тем лучше для вас.
Последняя разновидность повторного использования связана с
небольшой, но важной областью - XUL-тегом <textbox>. Операции с
ним реализованы в командах браузера Mozilla. Если даже все остальные
команды Mozilla не использовать в приложении, по крайней мере, эти
команды всегда помогут корректно организовать работу с элементами
формы.
До сих пор приложение NoteTaker для выполнения своих задач
использовало лишь простую функцию action(). NoteTaker - простое
приложение, не требующее сложной инженерной работы. Но для тренировки
оформим действия, выполняемые приложением, как команды. Чтобы этого
добиться, мы должны:
Первый шаг - планирование. NoteTaker имеет фиксированную
функциональность, так что у нас должен получиться компактный список
команд. Более того, команды нашего приложения всегда находятся в
состоянии enabled. Вместе эти два условия означают, что контроллер,
управляющий командами, неизменен, и, следовательно, у нас не будет
событий commandupdate. Это очень простой случай статической системы
команд.
Какие выбрать имена команд - вопрос. Когда мы используем систему
команд, мы пользуемся общим для всей платформы пространством имен. В
диалоговом окне Edit это не столь важно, потому что здесь нет
прикладного кода другого приложения. В главном же окне браузера панель
инструментов NoteTaker и элементы меню Tools могут иметь общие
команды. Имена команд, которые мы создадим, не должны конфликтовать с
уже существующими.
Для решения этой проблемы существует два подхода. Первый - выбрать
общие имена, так что когда вызвана команда save, ее могут получить
несколько модулей, включая NoteTaker. Это сложная интеграционная
задача. Второй подход, который мы и выберем здесь, - попросту называть
команды, присоединяя к ним префикс "notetaker-". Это немного
громоздкий способ, но имя NoteTaker уникально, поэтому он радикально
решает проблему имен команд.
В предыдущей лекции мы разработали четыре возможных аргумента
функции action():
edit keywords cancel save
Эти задачи можно спроектировать лучше. Например, save сохраняет запись и закрывает диалоговое окно. Эти два шага могут быть отдельными командами. Уточненный список команд:
Tools и кнопкой Edit на панели инструментов.Cancel и кнопкой Save диалогового окна.onload диалогового окна Edit.Save на панели инструментов и в диалоговом окне Edit.Delete на панели инструментов.Edit. Вызывается комбинацией клавиш в диалоговом окне Edit.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) {}
};
По сути, этот объект - всего лишь обертка функции action(),
удовлетворяющая инфраструктуре 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) {}
};
Этот контроллер - поддельный. Он принимает и делает вид, что
выполняет все команды, и сообщает о своих действиях на консоль
JavaScript. Его можно использовать для регистрации запросов на
выполнение команд. Теперь оба контроллера реализованы, за исключением
того, что нужно немного позаботиться о функции action().
Наконец, контроллеры нужно установить в каком-либо объекте DOM. Мы
выберем для установки объект DOM тега <window> (объект AOM window ). Мы можем установить контроллер на одном из двух окон: главном
окне браузера (для панели инструментов и элемента меню Tools ), и
диалоговом окне Edit. Мы установим оба контроллера, notetaker и , в обоих окнах, т.е. появятся четыре контроллера. Оба окна
получат поддержку всех семи команд, но каждое окно будет обрабатывать
лишь свое подмножество команд. Вот простая функция, устанавливающая
контроллеры в одно окно.
function install_controllers()
{
window.controllers.appendController(notetaker);
window.controllers.appendController(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. до и после диспетчера
очень важны. Благодаря им мы гарантируем, что диспетчеру будет с чем
работать, и что окно останется корректным после работы
диспетчера.
В окне браузера мы можем вместо данной функции использовать уже
существующую, goDoCommand(). Эта функция находится в файле
globalOverlay.js архива 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) {};
На этом наши эксперименты с командами пока заканчиваются.
Когда взаимодействуют window. или window.content..В последних
версиях нет жесткой необходимости следить за фокусом, но это
по-прежнему хороший стиль программирования.
Если проблемы с фокусом все-таки появятся, есть опасность нарушить работу не только платформы Mozilla, ни и самого окружения рабочего стола. Завершить работу платформы не всегда достаточно, может потребоваться даже перезагрузка компьютера, по крайней мере, в Microsoft Windows.
В конце концов, команды - это просто строки, а строка может содержать все что угодно. Работая с командами, помните, что строки команд формируют свое пространство имен, с которым нужно обращаться аккуратно. Не поддавайтесь соблазну посылать произвольную команду из любого места кода, просто потому, что это возможно.
Существуют многочисленные примеры кода в платформе Mozilla и ее
приложениях, где не была реализована необходимая проверка данных на
корректность. Обычный пример - атрибут XML, имеющий булевские
значения. Обычно платформа проверяет их на значение "true",
и это значит, что в ином случае они могут иметь значение false. Это
быстрый стиль разработки, но он же делает отладку более трудной,
потому что невалидные значения или принимаются или молча игнорируются
системой. Это в особенности верно для команд, которые имеют добавочный
уровень сложности - диспетчер, между вызовом команды и ее
выполнением.
Разрабатывая приложение, следует всегда проверять строки команд: не должно быть значений по-умолчанию или нестандартных случаев. Подобные случаи - источник опечаток или иных незначительных ошибок, которые могут в данном случае обернуться большой бедой. В случае асинхронных задач, когда время выполнения команды назначается заранее, еще более важно проверять строки команд. Всегда необходимо точно знать все команды, генерируемые кодом.
Строгая проверка строк команд не реализована в платформе Mozilla,
потому что приложения должны быть открыты для улучшений. Для локальных
приложений нет оснований пренебрегать точным определением и проверкой
команд. Лишь в редких случаях, подобных расширяемым играм типа
Контроллер ", о котором шла речь в разделе
"Практика" настоящей лекции, позволяет удостовериться, что
все идет правильно. Этот контроллер никогда не должен получить
команду. Если он ее все же получит, значит, что-то неверно и требуются
исправления.
Заметим, что неизвестные команды и неизвестные события - разные вещи. Нет необходимости блокировать или улавливать неизвестные события. Такие события будут обработаны службой обнаружения событий, см. 6.6. "Отладка: обнаружение событий", обсуждавшейся в лекции 6, "События".
Платформа Mozilla предоставляет систему спецификации и
распространения команд. Эта система требует функционального анализа
приложения. Подобный функциональный анализ позволяет прикладному
программисту разделить задачи, выполняемые приложением в процессе
работы, и его графическую оболочку. Такое разделение требует ясности
при проектировании приложения. Это предохраняет реализацию системы
команд от изменений, которые могут повлечь изменения в более подвижной
и подверженной даже влияниям моды графической оболочке.
Это гибкая система. Для приложений она предоставляет небольшую
оболочку, расширяемую для конкретных целей приложения. Для небольших
Как и фокусное кольцо в предыдущей лекции,
В этой лекции рассматривается
Очень важно, чтобы задачи, выполняемые окном приложения, как-то отделялись от своих пользовательских интерфейсов. С одной стороны, это связано с тем, что XUL-интерфейсы легко поддаются изменениям, и во время проектирования, и во время работы приложения. Такие интерфейсы накладывают жесткое ограничение на архитектуру приложения. С другой стороны, многие существующие методики разработки программного обеспечения могут применяться к задачам приложения, если их можно выразить самостоятельно. Примеры таких методик - варианты использования и схемы иерархий. Отдельное определение задачи делает ее повторное использование более вероятным. В конце концов, такая стратегия разделения образует гибкую среду проектирования.
У профессионально созданного приложения все команды будут формально определены во время проектирования. Определение команд, которые нужно реализовать - часть процесса аналитического проектирования и может использоваться для поиска запутанных, дублирующих и беспорядочных действий. Такие проблемы проектирования в этом случае можно будет решить до этапа реализации, а не после.
Традиционные приложения на основе HTML часто состоят из самой запутанной части JavaScript-кода. Систему команд также можно рассматривать как попытку избавиться от этого неструктурированного подхода.
Иллюстрация в начале этой лекции демонстрирует те части Mozilla,
которые реализуют команды. Из рисунка видно, что [m]<key>[/m]. [m]<key>[/m], потому что у нее нет никакого
пользовательского интерфейса.
Найти команду в Mozilla очень легко. Простые операции вроде сохранения файла, добавления закладки, выбора содержимого, выделения полужирным шрифтом, прокрутки страницы или отмены - все это команды.
Обнаружить команду в терминах исходного кода не так легко. Самый
очевидный признак команды - простая строка, чье имя - имя команды.
Такие имена могут определяться платформой или программистом
приложения. К сожалению, остальная часть инфраструктуры команд
довольно сильно разбросана по платформе. Части
Простой пример использования
<?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>
Тег <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>
Этот код описывает объект control, реализующий команду; фактически,
данный объект может реализовать несколько команд. Команда реализована
в методе doCommand(). Затем объект нужно встроить в инфраструктуру
команд платформы. Он будет вызываться каждый раз, когда потребуется
выполнить данную команду. И, наконец, команда может быть вызвана в
любом месте документа XUL с помощью функции execute(). В данном случае
удобно использовать обработчик onclick.
Очевидно, второй пример сложнее первого, но есть серьезные основания считать этот подход лучшим.
Система распространения очень не похожа на традиционную клиент-
серверную архитектуру. В такой архитектуре серверы выполняют команды
независимо и удаленно от клиентского GUI. Зачастую они выполняются без
видимого эффекта. Появляется лишь сообщение об их выполнении.
Например, HTTP запросы GET и POST игнорируют графическую оболочку
клиентского браузера, они лишь возвращают код статуса, и, может быть,
новый документ.
Команды Mozilla не похожи на клиент-серверные команды. Они тесно
связаны с клиентской графической оболочкой. Примером может служить
операция Bold в компоновщике Mozilla (да и в любом текстовом
процессоре). Bold применяет операцию стиля на выделенном фрагменте,
который обычно виден на дисплее.
Последний абзац означает, что команды не "удалены" от
прикладного программиста. Они часть того кода, который он создает и
подключает к
Mozilla использует специальный дизайн кода, называемый Command
, "образец команды", чтобы отделить имена команд от
их реализации. Большинство современных пакетов разработки GUI включают
технологии, подобные Command . Ниже будет показано, как
работают такие образцы.
// 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
Объект не только кажется необоснованно сложным по сравнению
с простой функцией, но он вдобавок требует лишней глобальной
переменной для своей работы. Однако он гораздо более гибок, поскольку
имеет стандартизованный интерфейс. Все -объекты имеют
единственный метод , выполняющий реализованную функтором команду
и возвращающий лишь сообщение об ошибке или об успешном выполнении.
Таким образом, все
Объект enabled.
На практике большинство систем, реализующих команды, включая Mozilla,
в явном виде поддерживают состояние enabled. Прикладной код может
опрашивать это состояние, чтобы узнать, доступна ли команда, и затем
реагировать соответствующим образом. Объект может хранить
любую информацию состояния о команде, какая представляется удобной, а
не только состояние enabled. Другими состояниями могут быть имя
команды, уникальный id, флаги, означающие, готова ли команда к
выполнению, блокирована или оптимизирована; язык, на котором команда
реализована, или ее версия и т.д.
В данном примере объект модифицирует глобальную переменную
num при выполнении команды. Это позволяет методу не иметь
параметров, что делает все объекты в этом отношении
одинаковыми. num может быть свойством объекта , но это
неудачное решение. Это плохо, потому что состояние enabled ), реализация команды не использует информацию о
состоянии при выполнении. Информация о состоянии используется кодом,
который вызывает
так:
exec: function () { return really_halve_it(); }
В этом варианте функция really_halve_it() выполняет всю реальную
работу за команду и может быть реализована где-нибудь в другом месте,
например, в библиотеке.
В последнем примере объекта подчеркивается, что он не
содержит реализации команды. Это всего лишь точка доступа к команде и
ее состоянию. Это
В литературе о функторах, не связанной с Mozilla, метод
часто обозначают как execute(). В 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");
Объект controller содержит объект _cmds, устанавливающий
соответствие между именами свойств и supportsCommand() сообщает, знает ли
контроллер о команде; doCommand() выполняет onEvent() передает
Последний метод - isCommandEnabled() в данном случае является
некоторым жульничеством. Он должен проверять свойство enabled у
соответствующего true.
Это и потому также так, что ни один из приведенных enabled.
Кажется, что они должны реализовать три полноценных
Этот последний пункт важен. Поскольку контроллер полностью скрывает
реализацию команды (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");
В данном примере контроллер действует, как если бы все три
Сложные контроллеры также могут многое выиграть от явной реализации
команды. Иногда возникают проблемы с синхронизацией, которые лучше
всего решить в контроллере. Пример - реализация команды в
редакторах, подобных Classic . Контроллер отвечает за
реализацию конкретной команды, так что его логично использовать и для
сохранения controlling ).
Контроллеры также удобны для размещения в них макро-языков, механизмов
синхронизации, и всего, что связано с существованием команд.
Mozilla не ограничивается единственным статическим контроллером. Можно создать столько контроллеров, сколько вам требуется. Так сделано, например, в компоновщике Mozilla, где имеются три контроллера, каждый из которых реализует свой набор команд.
Все контроллеры должны быть помещены туда, где платформа сможет их найти. Их можно поместить в несколько разных мест.
Контроллеры можно поместить в объект window.
Несколько XUL-тегов являются подходящими местами. Можно
использовать <button>, <, <, <toolbarbutton>, <menu>, <menuitem>, и <key>.
XUL-тег <command> подобен
Использование каждого из этих мест будет рассматриваться позднее, при обсуждении синтаксиса команд.
Если в каком-то месте имеется более одного контроллера, то набор контроллеров называется цепочкой контроллеров. Такое множество контроллеров упорядоченно, и при необходимости они просматриваются по очереди. Это значит, что первый контроллер в цепочке, который может выполнить команду, и будет тем контроллером, который ее выполнит.
Каждый контроллер содержит набор команд; в одном месте может быть ноль или более контроллеров; диспетчер контроллеров работает с набором таких мест. К счастью, это все. Задачей диспетчера контроллеров является поиск и выполнение конкретной команды.
Mozilla имеет один диспетчер для HTML-документов и один - для
документов XUL. Диспетчер HTML невидим и недоступен скриптам ни при
каких обстоятельствах. С диспетчером команд XUL можно работать. Каждое
окно XUL-приложения имеет объект в корне
Диспетчер имеет метод getControllerForCommand(). Данный метод
получает имя команды в виде строки и возвращает контроллер, способный
выполнить эту команду. Диспетчером и возвращаемым контроллером нужно
явным образом управлять из скрипта - они ничего не делают
автоматически. В файлах goDoCommand(). Эта функция
доступна в каждом окне классической Mozilla и применяется чаще
всего.
Диспетчер использует информацию о состоянии текущего фокуса, чтобы
решить, какой контроллер нужен для данной команды. Эта информация о
состоянии включает состояние фокуса окна, все фокусы тегов <iframe>, и любой элемент DOM в фокусном кольце, если он имеет в
настоящее время фокус. Другими словами, диспетчеру нужна информация об
иерархии фокуса. Диспетчер начинает с наиболее глубокого,
специфичного положения фокуса (обычно это элемент формы или меню), и
переходит по дереву DOM вверх вплоть до верхнего окна. На каждом
элементе DOM, способном получить фокус, он исследует цепочку
контроллеров в Command(). Первый же объект controller, реализующий выполнение нужной
команды, возвращается. Если на данном уровне результата не находится,
диспетчер переходит дальше вверх по фокусной иерархии. Если он доходит
до верха, не найдя результата, он возвращает значение null.
Окно XUL и его контент не полностью инициализируют фокус при создании окна. Даже если окно является текущим окном экрана, оно может не иметь фокуса. Это значит, что имеющиеся контроллеры не всегда автоматически доступны, даже если они находятся в вершине текущего окна. Программист должен явно дать окну фокус, если контроллер этого окна должен быть доступен. Простейший способ сделать это - одна строка скрипта:
window.focus();
Важно, чтобы фокус был установлен и до, и после выполнения команды. Если этого не сделать, кольцо фокуса может быть разрушено, и рано или поздно могут возникнуть проблемы. Это ограничение было исправлено в версии платформы 1.4, но явное указание фокуса все еще рекомендуется. Диспетчер не просматривает все подряд в поисках реализации команды.
Если виджет, имеющий фокус, имеет атрибут command, диспетчер не
принимает его во внимание. Он исследует только контроллеры.
Одно важное свойство
Команды можно создавать и управлять ими, их можно искать и
выполнять, но что будет, если команда изменится? Предположим,
состояние команды enabled изменит значение с true на false. Как та
часть приложения, которая использует команду, узнает, что команда
более недоступна? Извещение об изменении решает эту проблему.
В лекции 6, "События", мы рассматривали шаблон
проектирования "
Диспетчер пользуется списком наблюдаемых тегов XUL в форме объектов
DOM. Это обычные объекты DOM, но они называются объектами,
обновляющими команды ( command updaters ), обновителями команд, когда
используются в этом качестве. Диспетчер может добавлять и удалять эти
объекты из списка.
Событие DOM генерируется и передается системе обработки событий с
помощью метода dispatchEvent(). Извещение об изменении команды
передается системе "UpdateCommands() диспетчера или окна. Все наблюдатели,
ожидающие некоторого события, получат его. В результате такое событие
действует точно так же, как и любое событие, которое может получить
XUL-тег.
События обновления команд работают в XUL, но не в HTML. Далее мы коротко рассмотрим синтаксис и способы использования таких событий.
Команды в Mozilla могут быть вызваны различным образом.
Если объекты контроллер или
Из JavaScript может быть вызван метод диспетчера getControllerForCommand(). Этот метод - первый шаг по превращению
имени команды в выполняемый код. В этом случае диспетчер ищет
необходимую нам реализацию команды.
Из JavaScript может быть вызван метод doCommand() XUL-тега,
способного иметь фокус. Если это выполнено, тег <command>,
указанный как значение атрибута команды, будет выполнен. При этом мы
не используем диспетчер или контроллеры.
Если нажата клавиша, имеющая соответствующий тег <key>, или
мы щелкнули мышью на теге, который может иметь фокус, будет выполнена
команда, указанная как значение атрибута command. Это тот же случай,
как вызов метода doCommand(), за исключение того, что тег или клавиша
сначала порождают событие command. Платформа имеет специальный
механизм для определения
Если порождено такое событие DOM, метод dispatchEvent() может быть
вызван из любого тега XUL. Если тег принадлежит фокусному кольцу, то
вызов этого метода эквивалентен пользовательскому вводу, порождающему
такое событие. То есть данный случай эквивалентен предыдущему.
Во всех трех описанных случаях выполняемая команда может обратиться
к диспетчеру. Это свяжет тег <command> с контроллером.
Наконец, если вызван метод диспетчера UpdateCommands(), любое
сообщение об изменении команды будет послано всем XUL-тегам,
наблюдающим за изменениями. Это означает, что событие commandupdate
будет послано всем обновителям команд ( command updaters ).
Приложения классической Mozilla включают функцию, называемую goDoCommand(). Эта функция управляет диспетчером. Это стартовая точка
команд в таких приложениях. Она хранится в файле globalOverlay.js
архива
Данный краткий обзор
Команда может войти в систему распространения команд из обычного
JavaScript. Этот скрипт может сам по себе быть обработчиком событий,
возможно, для более традиционных событий DOM, таких как события click
или select. В этом случае скрипт должен быть привязан к тегу XUL.
Команда может выйти из системы распространения команд и в обычном
JavaScript, реализующем команду. Можно написать
Наконец, этот тег XUL может получать извещения об изменении состояния команды по системе обновления команд.
Все вместе это означает, что команда может начаться и кончиться в
теге XUL (или XUL и JavaScript). Например, тег <toolbarbutton>,
порождающий команду, может получить сообщение, что состояние команды
изменилось. Этот же тег <toolbarbutton> может содержать и
реализацию контроллера команды. Диспетчер и стандартный механизм
обработки событий - лишь кусочки головоломки, созданной платформой
Mozilla.
Для команд в Mozilla есть специальный тег, <command>. Можно
использовать также <commandset> и <commands>, но это
определяемые пользователем теги, не имеющие собственного значения. Тег <commandset> используется как любой иной тег-контейнер,
например, <keyset>, и содержит набор тегов <command>.
XUL также имеет набор специальных атрибутов, применимых к любому тегу. Это следующие атрибуты:
command events targets commandupdater
и два обработчика событий:
oncommand oncommandupdate
Вне рамок XUL Mozilla также имеет набор заранее определенных имен команд, но мы обсудим это позже.
Тег <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 имеет свой тег <, позволяющий обычному событию DOM,
подобно событию click, передаваться в указанную команду. Для этого в
XBL-теге < используется атрибут command.
Если XUL-тег имеет эти атрибуты, он обладает способностью обновлять
команды и будет получать извещения об обновлении команд. Эти извещения
- часть системы "UpdateCommands(), все зарегистрированные наблюдатели
получают извещения о событии. В результате, если происходит событие commandupdate, запускается обработчик события oncommandupdate данного
тега. Пример такого кода прост:
<mytag commandupdater="true" oncommandupdate="update_all()"/>
Функция update_all() - некоторый конкретный скрипт данного
приложения. Он делает всю необходимую работу с изменившейся командой.
Например, такая функция есть в классической Mozilla. Файл
globalOverlay.js архива goUpdateCommand() (и иные функции go...). Эта функция выполняет
стандартные процедуры обновления команд. Она устанавливает атрибут disabled тега <command>, вызывая метод контроллера isCommandEnabled().
Дальнейшие примеры обновления команд приведены в разделе "Как заставить виджет реагировать на состояние команды". Тегу, способному обновлять состояние команды, можно указать и такой фильтр:
<mytag commandupdater="true" events="focus blur" targets="*" oncommandupdate="update_all()"/>
Атрибут events ограничивает commandupdate. Это может быть targets имеет
тот же синтаксис, за исключением того, что вместо списка имен
используется список идентификаторов ( ). Делать это за редким
исключением не рекомендуется. Таким способом мы ограничиваем список
рассматриваемых событий теми, чьи цели (теги) включены в список .
Значение по умолчанию обоих атрибутов равно "*", то есть
"все".
Даже если атрибут targets указан, событие commandupdate передается
тегу, способному изменить состояние команды, но не тегам - целям. Ниже
мы увидим, как используется данная функциональность.
Событие commandupdate можно вызвать из JavaScript, как и событие command. Чтобы это сделать, следует вызвать метод диспетчера UpdateCommands(). В случае команд, определенных в XUL, это не слишком
гибкий и полезный подход, потому что простые обработчики событий,
используемые в oncommand, не имеют состояний. Только если команда
реализуется JavaScript (то есть используется объект controller ), вызов
события commandupdate имеет смысл.
Тег <commandset> определяется программистом и не имеет
специального значения. Его используют, чтобы сгруппировать команды
вместе.
Иногда приложение XUL состоит из множества отдельных файлов. В этом
случае тег <commandset> зачастую имеет id, по которому его можно
найти в результирующем документе, тем же способом, который описан в
лекции 12, "Оверлеи и
Он используется и при обновлении команд. Это удобно, поскольку его
дочерние теги обычно являются тегами <command>. Когда
генерируется событие commandupdate, обработчик oncommandupdate тега <commandset> просматривает по очереди все дочерние теги и
проверяет их состояние. Такая стратегия используется в
Наконец, тег <commands> также является тегом, определяемым
программистом и не имеющим специального значения. Он используется лишь
для группирования тегов <commandset>. Этот тег тоже применяется
в системе оверлеев Mozilla. В результате все теги <commandsets>,
разбросанные по файлам приложения, окажутся дочерними тегами тега <commands>.
Прямое использование 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, чье множество содержит единственный контроллер.
Этот единственный контроллер называется контроллером фокуса и
используется для управления фокусом окна и событиями в фокусном
кольце.
Разработчик прикладных программ наиболее заинтересован в
конкретных, специфичных для данного приложения контроллерах. По
умолчанию таких контроллеров нет. Даже после создания они достижимы
только из
nsIControllerCommand, который выглядит следующим образом, если его
реализовать на JavaScript:
var functor = {
isCommandEnabled : function (cmd) { ... },
getCommandStateParams : function (cmd) { },
doCommandParams : function (cmd) { },
doCommand : function (cmd) { ... },
}
Обычно
Далее
Если контроллер существует,
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, последний этап реализации - window:
window.focus()
Если этого не сделать, фокус должен установить какой-либо иной код, либо сам пользователь, до того, как некоторая команда будет вызвана.
<tree> в диалоговом окне адресной книги. Начните изучение с
файла abCommon.js в архиве
Одна из главных целей , Copy, и Paste, должно иметь операцию Paste в виде,
означающем недоступность, если пока еще ничего не вырезано или не
скопировано в буфер. Любая информация о состоянии, отражающая эти
изменения, должна храниться в команде, реализующей операцию Paste, а
не в самом виджете. Потому что операция Paste может быть доступна
пользователю из нескольких разных виджетов, например меню или панели
инструментов.
<updater commandupdater="true" oncommandupdate="update()"> <command id="paste" oncommand="doPaste()"/> </updater> <button label="Paste1" command="paste" observes="paste"/> <description value="Paste Enabled" observes="paste"/>
Тег <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");
}
}
Эта функция знает только о команде Paste. Она ищет реализацию этой
команды и проверяет ее состояние с помощью функций isEnabled() и clipboardEmpty(). isEnabled() - стандартный метод контроллера, но
данный контроллер должен иметь добавочный метод clipboardEmpty(). Эта
конкретная команда имеет два состояния: доступна ли она и есть ли что-
то в буфере обмена. Если в буфере обмена ничего нет, то нечего и
вставить в текст.
На основании анализа этих состояний функция update() загружает тег <command> с необходимыми атрибутами. Эти атрибуты не являются
значащими для самого тега <command>. Они могут влиять на
графические виджеты. И <button>, и <description> получают
эти атрибуты, но для <button> значащим является атрибут disabled, а для <description> - value.
В результате теги виджетов будут обновлены, и их внешний вид
изменится. Все это происходит в результате единственного вызова метода UpdateCommands() диспетчера команд, начинающего процесс извещения об
изменении/изменениях.
Здесь существуют две системы "<updater> как тега, обновляющего команду. Вторая -
специфичный для данного приложения атрибут update(),
например, используя идентификаторы тегов - , либо как-то иначе.
Хотя
Не следует рассматривать объекты и интерфейсы XPCOM применительно к таким модификациям и улучшениям. Политика именования объектов в данном случае не помогает, слишком мало ключевых слов. В результате разные интерфейсы по названию становятся очень похожими.
Единственный способ решить эту проблему - подробно охарактеризовать лишь сами интерфейсы. Таблица 9.1. следует версии платформы 1.4. Лишь некоторые из этих интерфейсов в настоящее время приняли завершенный вид, в более поздних версиях они могут немного меняться.
| Имя интерфейса | Существующая реализация XPCOM | Цель |
|---|---|---|
nsICommand |
Хотя имя кажется логичным, такого интерфейса нет | |
nsICommandHandler |
@mozilla.org/ |
Реализация команд нижнего уровня - не используется в JavaScript |
nsICommandHandlerInit |
@mozilla.org/ |
Используется для 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 | Используется для группирования изменяемого множества команд |
nsIControllerCommand |
None | Базовый |
nsICommandManager |
@mozilla.org/embedcomp/command-manager;1 | Используется для управления вспомогательными методами |
nsICommandParams |
@mozilla.org/embedcomp/command-manager;1 | Структура данных, используемая как список параметров при выполнении команды с параметрами |
nsIControllers |
None | Набор контроллеров как свойство элемента DOM |
NsIDOMXULCommandDispatcher |
Диспетчер, не являющийся компонентом XPCOM | Диспетчер команд XUL |
nsPICommandUpdater |
Внутренний интерфейс, не используемый в приложениях | |
nsISelectionController |
Не работает с системой распространения команд | |
nsIEditorController |
@mozilla.org/editor/composercontroller;1 @mozilla.org/editor/editor-controller;1 | Два контроллера, использующиеся в приложении Mozilla |
nsITransaction |
@mozilla.org/ |
Три интерфейса, полезные, когда контроллер становится слишком сложен. Используйте их для реализации , макросов, |
nsITransactionList |
||
nsITransactionManager |
Существуют также несколько интерфейсов для реализации синтаксиса командной строки. Их можно использовать для запуска платформы, но они не имеют дела с системой распространения команд.
До сих пор мы рассматривали аспекты XUL, AOM и XPCOM системы
распространения команд платформы Mozilla. Mozilla, однако, - не только
платформа, но и законченное приложение (классическая Mozilla).
Некоторые команды приложения можно применять и в платформе. Иногда
программисты приложений используют это свойство платформы. Простой
пример повторного использования существующей технологии - контроллер
фокуса. Этот контроллер всегда доступен в окне XUL. Диспетчер команд
задействует методы advanceFocus() и rewindFocus(), обеспечивающие
управление фокусом, которое обычно пользователь осуществляет с помощью
клавиши Tab. Названные методы посылают команды контроллеру фокуса.
Этот контроллер можно использовать и для обновления команд. Тег,
который должен быть обновлен, может получать извещение об обновлении
команды, как результат некоторого события, произошедшего с фокусом.
Почему контроллер посылает два события commandupdate на каждый шаг
навигации по кольцу фокуса, мы объясним позже.
Другая разновидность повторного использования кода - заимствование.
[theft - дословно "
Сверх этого повторное использование кода команд Mozilla
затруднительно. Но есть один специальный случай эффективного
использования существующей
Набор приложений Mozilla
Таким образом, если ваше приложение подобно классической Mozilla
(например, еще одна вариация браузера), то эти команды могут
эффективно использоваться. Если предстоит глубокая перестройка
существующих приложений Mozilla, то знакомство с их системами команд
очень существенно. Если ваше приложение -
Полезно ознакомиться с кодом команд классической Mozilla, доступным
в файле ComposerCommands.js в архиве
Не стоит слишком полагаться на возможности повторного использования существующего кода. В конце концов, успех нового приложения зависит как раз от его новизны, а не от того, насколько удачно он имитирует что-то уже хорошо известное. Если ваше приложение работает в точности как уже существующее, то какой смысл его разрабатывать? Большинство новых приложений должно содержать как минимум несколько новых команд. Если вы сможете повторно использовать часть кода на этом пути, тем лучше для вас.
Последняя разновидность повторного использования связана с
небольшой, но важной областью - XUL-тегом <textbox>. Операции с
ним реализованы в командах браузера Mozilla. Если даже все остальные
команды Mozilla не использовать в приложении, по крайней мере, эти
команды всегда помогут корректно организовать работу с элементами
формы.
До сих пор приложение NoteTaker для выполнения своих задач
использовало лишь простую функцию action(). NoteTaker - простое
приложение, не требующее сложной инженерной работы. Но для тренировки
оформим действия, выполняемые приложением, как команды. Чтобы этого
добиться, мы должны:
Первый шаг - планирование. NoteTaker имеет фиксированную
функциональность, так что у нас должен получиться компактный список
команд. Более того, команды нашего приложения всегда находятся в
состоянии enabled. Вместе эти два условия означают, что контроллер,
управляющий командами, неизменен, и, следовательно, у нас не будет
событий commandupdate. Это очень простой случай статической системы
команд.
Какие выбрать имена команд - вопрос. Когда мы используем систему
команд, мы пользуемся общим для всей платформы пространством имен. В
диалоговом окне Edit это не столь важно, потому что здесь нет
прикладного кода другого приложения. В главном же окне браузера панель
инструментов NoteTaker и элементы меню Tools могут иметь общие
команды. Имена команд, которые мы создадим, не должны конфликтовать с
уже существующими.
Для решения этой проблемы существует два подхода. Первый - выбрать
общие имена, так что когда вызвана команда save, ее могут получить
несколько модулей, включая NoteTaker. Это сложная интеграционная
задача. Второй подход, который мы и выберем здесь, - попросту называть
команды, присоединяя к ним префикс "notetaker-". Это немного
громоздкий способ, но имя NoteTaker уникально, поэтому он радикально
решает проблему имен команд.
В предыдущей лекции мы разработали четыре возможных аргумента
функции action():
edit keywords cancel save
Эти задачи можно спроектировать лучше. Например, save сохраняет запись и закрывает диалоговое окно. Эти два шага могут быть отдельными командами. Уточненный список команд:
Tools и кнопкой Edit на панели инструментов.Cancel и кнопкой Save диалогового окна.onload диалогового окна Edit.Save на панели инструментов и в диалоговом окне Edit.Delete на панели инструментов.Edit. Вызывается комбинацией клавиш в диалоговом окне Edit.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) {}
};
По сути, этот объект - всего лишь обертка функции action(),
удовлетворяющая инфраструктуре 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) {}
};
Этот контроллер - поддельный. Он принимает и делает вид, что
выполняет все команды, и сообщает о своих действиях на консоль
JavaScript. Его можно использовать для регистрации запросов на
выполнение команд. Теперь оба контроллера реализованы, за исключением
того, что нужно немного позаботиться о функции action().
Наконец, контроллеры нужно установить в каком-либо объекте DOM. Мы
выберем для установки объект DOM тега <window> (объект AOM window ). Мы можем установить контроллер на одном из двух окон: главном
окне браузера (для панели инструментов и элемента меню Tools ), и
диалоговом окне Edit. Мы установим оба контроллера, notetaker и , в обоих окнах, т.е. появятся четыре контроллера. Оба окна
получат поддержку всех семи команд, но каждое окно будет обрабатывать
лишь свое подмножество команд. Вот простая функция, устанавливающая
контроллеры в одно окно.
function install_controllers()
{
window.controllers.appendController(notetaker);
window.controllers.appendController(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. до и после диспетчера
очень важны. Благодаря им мы гарантируем, что диспетчеру будет с чем
работать, и что окно останется корректным после работы
диспетчера.
В окне браузера мы можем вместо данной функции использовать уже
существующую, goDoCommand(). Эта функция находится в файле
globalOverlay.js архива 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) {};
На этом наши эксперименты с командами пока заканчиваются.
Когда взаимодействуют window. или window.content..В последних
версиях нет жесткой необходимости следить за фокусом, но это
по-прежнему хороший стиль программирования.
Если проблемы с фокусом все-таки появятся, есть опасность нарушить работу не только платформы Mozilla, ни и самого окружения рабочего стола. Завершить работу платформы не всегда достаточно, может потребоваться даже перезагрузка компьютера, по крайней мере, в Microsoft Windows.
В конце концов, команды - это просто строки, а строка может содержать все что угодно. Работая с командами, помните, что строки команд формируют свое пространство имен, с которым нужно обращаться аккуратно. Не поддавайтесь соблазну посылать произвольную команду из любого места кода, просто потому, что это возможно.
Существуют многочисленные примеры кода в платформе Mozilla и ее
приложениях, где не была реализована необходимая проверка данных на
корректность. Обычный пример - атрибут XML, имеющий булевские
значения. Обычно платформа проверяет их на значение "true",
и это значит, что в ином случае они могут иметь значение false. Это
быстрый стиль разработки, но он же делает отладку более трудной,
потому что невалидные значения или принимаются или молча игнорируются
системой. Это в особенности верно для команд, которые имеют добавочный
уровень сложности - диспетчер, между вызовом команды и ее
выполнением.
Разрабатывая приложение, следует всегда проверять строки команд: не должно быть значений по-умолчанию или нестандартных случаев. Подобные случаи - источник опечаток или иных незначительных ошибок, которые могут в данном случае обернуться большой бедой. В случае асинхронных задач, когда время выполнения команды назначается заранее, еще более важно проверять строки команд. Всегда необходимо точно знать все команды, генерируемые кодом.
Строгая проверка строк команд не реализована в платформе Mozilla,
потому что приложения должны быть открыты для улучшений. Для локальных
приложений нет оснований пренебрегать точным определением и проверкой
команд. Лишь в редких случаях, подобных расширяемым играм типа
Контроллер ", о котором шла речь в разделе
"Практика" настоящей лекции, позволяет удостовериться, что
все идет правильно. Этот контроллер никогда не должен получить
команду. Если он ее все же получит, значит, что-то неверно и требуются
исправления.
Заметим, что неизвестные команды и неизвестные события - разные вещи. Нет необходимости блокировать или улавливать неизвестные события. Такие события будут обработаны службой обнаружения событий, см. 6.6. "Отладка: обнаружение событий", обсуждавшейся в лекции 6, "События".
Платформа Mozilla предоставляет систему спецификации и
распространения команд. Эта система требует функционального анализа
приложения. Подобный функциональный анализ позволяет прикладному
программисту разделить задачи, выполняемые приложением в процессе
работы, и его графическую оболочку. Такое разделение требует ясности
при проектировании приложения. Это предохраняет реализацию системы
команд от изменений, которые могут повлечь изменения в более подвижной
и подверженной даже влияниям моды графической оболочке.
Это гибкая система. Для приложений она предоставляет небольшую
оболочку, расширяемую для конкретных целей приложения. Для небольших
Как и фокусное кольцо в предыдущей лекции,
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.