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

    В этой главе объясняется, как улучшить язык XUL с помощью новых тегов и новых свойств, используя XBL (XML Binding Language - Язык связок XML).

    XBL - основанный на XML язык, позволяющий конструировать новые мощные теги для XUL, HTML и XML. Это эффективный механизм и для создания новых графических элементов управления (GUI widgets). Он используется только в Mozilla.

    Простой XUL позволяет пользователю создавать собственные теги, наподобие <mytag>, но они не слишком полезны. Их можно оформить стилями, но они остаются простыми <box>-подобными тегами. С помощью же XBL, напротив, можно создавать целые виджеты с уникальным поведением и обликом. Контент XBL-виджетов комбинируется из XUL, HTML и других XBL-тегов. XBL не столь гибок как Java-апплет или плагин. Его конструирование нельзя начать с чистого прямоугольного листа и заполнять его, используя мощные графические библиотеки. XBL может лишь комбинировать уже существующие теги.

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

    <binding id="smiley"> 
      <content> 
        <xul:image src="face.png"/> 
      </content>
      <handlers> 
        <handler event="click" action="alert('have a day')"/>
      </handlers> 
    </binding>
    
    Листинг 15.1. Простейший пример XBL-связки.

    Эту связку можно привязать к тегу с именем <smiley/> с помощью следующей строки CSS:

    smiley { -moz-binding: url("smiley.xml#smiley"); }
    

    Теперь тег <smiley/> всегда будет отображать улыбку в документе XUL. Если по нему кликнуть, появится окошко с приветствием. Это несложный виджет со своей простейшей интерактивностью. Ни в XUL, ни в HTML тега <smiley> нет.

    Система XBL встроена в C/C++ код платформы Mozilla, но сами пользовательские связки пишутся на JavaScript и XML. Эта интерпретируемая среда позволяет создавать связки так же легко, как XUL или HTML. Простота применения интенсивно используется в Mozilla. Множество XBL-связок вносят свой вклад в приложения Mozilla, такие как классический почтовый клиент или классический браузер. Почти каждый XUL тег имеет XBL-связку.

    На протяжении всей этой книги мы часто ссылались на файлы .xml в архиве toolkit.jar директории chrome. Эти .xml и есть XBL-связки, обычно их бывает по нескольку на файл. Некоторые теги XUL, описанные в этой книге есть не что иное, как XBL связки за работой. Хороший пример - высокоспециализированные теги как <tabbrowser> и <colorpicker>, которые определены полностью средствами XBL. Даже простые теги, например, <button>, имеют XBL-связки.

    За пределами мира Mozilla технология, подобная XBL - это язык PostScript. PostScript используется в процессе печати, но PostScript- скрипты можно писать и вручную. PostScript дает возможность кусочкам печатаемого контента иметь имя и использоваться повторно. Таким образом легко создавать большие документы. Языки обработки списков, такие как Lisp и Tcl/Tk, также дают интерпретационный способ создания GUI, но они больше похожи на программы, чем XBL, основанный на XML. Вероятно, Tcl/Tk - самый большой конкурент XBL в мире Linux. Готовые компоненты в .NET - также конкуренты XBL в мире Microsoft Windows.

    Связки XBL имеют ясно определенный интерфейс и уникальность, что делает XBL небольшой компонентной системой. Как XPCOM предназначен для компонентов 3GL, XBL предназначен для компонентов XML. Компонентная модель XBL много проще, чем XPCOM.

    Поскольку связки XBL создаются с помощью XML и JavaScript, их можно свободно перемешивать с HTML или XUL. HTML используется нечасто, потому что группа тегов, применяемых как HTML, фиксирована и стандартна. Гораздо чаще можно увидеть XBL в приложениях XUL.

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

    Здесь мы продолжим общее описание системы XBL и затем перейдем к рассмотрению специальных тегов.

    15.1. Общая концепция XBL-связок

    XBL расширяет XML так же, как C++ расширяет C. Он добавляет объектно-ориентированные возможности системе, у которой средств для работы с объектами не было.

    15.1.1. Пример XBL-связки

    Каждый документ XBL - это просто список связок (bindings), каждая из которых начинается с тега <binding>. Конструирование связки подобно конструированию класса объектов. В Листинге 15.2 показана простая связка XBL, являющаяся вариантом тега-связки <checkbox>.

    <?xml version="1.0"?> 
    <bindings xmlns="http://www.mozilla.org/xbl"
      xmlns:xbl="http://www.mozilla.org/xbl"
    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" > 
      <binding id="checkbox" extends="general.xml#basetext"> 
        <resources> 
          <stylesheet src="chrome://global/skin/checkbox.css"/> 
        </resources> 
        <content>
          <xul:image class="checkbox-check"
            xbl:inherits="checked,disabled"/>
          <xul:hbox> 
          <xul:image class="checkbox-icon" xbl:inherits="src"/>
          <xul:label xbl:inherits="xbl:text=label,accesskey,crop"/> 
          </xul:hbox>
        </content> 
        <implementation implements="nsIDOMXULCheckboxElement">
          <method name="check" action="this.checked = true"/> 
            <property name="checked" onset="if (val) 
              this.setAttribute('checked','true');"
              onget="return this.getAttribute('checked') == 'true';" />
        </implementation> 
        <handlers> 
          <handler event="click" button="0" 
            action="if (!this.disabled) this.checked = !
              this.checked;"/> 
          <handler event="keypress" key=" "> 
            <![CDATA[ this.checked = !this.checked; ]]>
          </handler> 
        </handlers> 
      </binding> 
    </bindings>
    
    Листинг 15.2. Пример связки XBL, подобной тегу-связке <checkbox>.

    Листинг 15.2 демонстрирует многие стандартные черты связок. Разделы <resource> и <content> описывают данные в форме стилей, изображений, тегов и других простых элементов XUL. Эти разделы содержат контент XML. Разделы <implementation> и <handlers> описывают свойства, методы и связанные с событиями хуки. Эти секции существуют в форме DOM и JavaScript. Содержательные и скриптовые стороны связок подобны соединению кода и данных в программах для PC или Макинтоша. На границе - XUL и код JavaScript. Он связывает части, но сам не является частью языка XBL.

    Рекомендуем изучать связки, созданные другими людьми. В разделе "Чтение чужого кода" главе 16, "Объекты XPCOM", и в Таблице 16.1 приведены некоторые советы, облегчающие изучение связок, созданных командой mozilla.org.

    15.1.2. Стандарты

    XBL - это приложение XML. Его XML-определение можно найти по адресу www.mozilla.org/xbl. Mozilla не грузит этот URL, он используется лишь как идентификатор. Стандартным суффиксом для XBL-файлов является .xml.

    Стандартный тип MIME для документов XBL - application/xml.

    Связки XBL идентифицируются с помощью URL. Специальной схемы URL для XBL не существует, используются схемы http: и chrome:. Так же, как для RDF, URL связки включают суффикс #id, идентифицирующий каждую связку внутри документа XBL. Как и в случае с RDF, каждая связка, идентифицируемая по #id, рассматривается как целый ресурс, а не фрагмент ресурса. Пример:

    chrome://global/content/bindings/button.xml#button.
    

    XBL был принят консорциумом W3C как "предложение" (Note). Это означает, что документ, описывающий XBL, был предложен в консорциум со стороны, для дальнейшего рассмотрения. Это не черновик стандарта (draft) и не стандарт ("on the standards track"), а всего лишь предложение. Последнюю версию "XBL Note" можно найти по адресу http://www.mozilla.org/projects/xbl/xbl.html.

    Реализация XBL в Mozilla отличается от предложенной в W3C. Она содержит добавочные особенности и не реализует полностью все свойства, описанные в "Предложении". Разница рассматривается в разделе "Не-теги и не-атрибуты" данной главе.

    Стандарты, конкурирующие с XBL, это ECMA-290 "ECMAScript Components", используемый Microsoft в технологии WSH (Windows Scripting Host). Стандарт WSDL и связанные с ним стандарты также предлагают распределенную систему именования, но WSDL не так тесно увязан с взаимодействием с пользователем, как XBL. Наполовину конкурирующим стандартом является XSL. XSL может обрабатывать файлы XML и заменять указанные теги другим контентом. Это то же самое, что делает XBL, но XSL работает в пакетном режиме, просматривая XML документ от начала до конца один раз. XBL же может работать с документом после его полной загрузки. Никакие иные стандарты не нацелены на ту нишу, которую занимает XBl.

    XBL имеет более широкую область применения, чем любой иной стандарт W3C. HTML, CSS и JavaScript сами могут взаимодействовать весьма ограниченно. Эти три стандарта фокусируются на единственном теге, стиле или объекте в один момент времени. Хотя эти стандарты позволяют тегу, стилевому правилу и свойству объекта сгруппироваться в единое целое, такая группировка весьма узка. Для программиста нет структурной поддержки, охватывающей все три области и предлагающей высокоуровневые объектно-ориентированные удобные концепции. XBL исправляет этот недостаток, интегрируя XML, скрипты и стили в одну структуру - "связку" (binding).

    15.1.3. Взаимодействие с иерархией DOM.

    Чтобы связки XBL можно было использовать, они должны быть предварительно встроены в иерархию DOM.

    Перед тем, как связка будет использована, ее нужно связать с существующими XML-тегами документа. Эти теги называются граничными тегами (bound tag), а документ - целевым документом. Если граничный тег имеет собственный контент, он называется эксплицитным контентом. Если же сама связка имеет тег <content>, то теги внутри него называются анонимным контентом. Есть только один набор анонимного контента на связку, но могут быть различные наборы эксплицитного контента на каждый граничный тег в целевом документе. Оба типа контента, плюс остальные детали связки, добавляются документу, эту связку использующему.

    Когда мы вызываем XBL-связку, иерархия DOM целевого документа растет. Возникают три изменения:

    1. Граничный тег приобретает свойства, методы и обработчики событий связки.
    2. Контент граничного тега заменяется смесью эксплицитного и анонимного контента.
    3. Анонимный контент добавляется в иерархию DOM отдельно, для доступа к нему предусмотрен специальный механизм.

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

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

    Эти принципы подробно рассматриваются в разделе "Как обрабатываются связки".

    15.1.4. Действия по умолчанию

    Систему XBL можно использовать для реализации действий, выполнения которых требуют HTML и XUL-теги.

    Такое действие по умолчанию можно видеть на примере тега HTML <input type="submit">. Когда мы кликаем по кнопке виджета, принадлежащего этому тегу, данные из формы посылаются браузером на некий web-сервер. Не нужно никакого скрипта, чтобы это произошло. Действие по умолчанию - часть обработки событий DOM, реализованная для всех тегов XML. Но как это реализовано, и как это событие встраивается в обработку событий DOM?

    Ответ состоит в том, что действие по умолчанию можно встроить в XBL-связку. Связка дает способ связать вместе событие DOM и обработчик JavaScript. Когда порождается событие, связка XBL автоматически определяется как источник действия по умолчанию, запускается необходимый обработчик и таким образом выполняется требуемое действие.

    Данный виджет может иметь различное действие по умолчанию для каждого порождаемого пользователем события. В случае тега <input type="submit"> нажатие клавиш Return или Enter или клик по изображенной кнопке произведут одинаковый эффект. Но это не обязательно так для произвольной связки - каждое событие может иметь свою реакцию по умолчанию.

    Разработчик приложения может изменить действие по умолчанию, используя обычный обработчик событий. К этой задачке, однако, нужно подходить аккуратно. Если обработчик события установлен в форме значения атрибута (как, например, onclick), то будет использован именно он, а не обработчик XBL. Если обработчик события установлен с помощью функции addEventListener(), то этот обработчик должен быть установлен до того, как обработчик XBL будет включен в тег. Это требование ставит обработчики приложения перед обработчиками XBL в списке обработчиков события. См. замечания в разделе "Скриптинг".

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

    15.1.5. Объектные свойства связок

    Связки XBL имеют объектно-ориентированные свойства и могут рассматриваться как объекты с точки зрения JavaScript или DOM.

    Интерфейс объекта имеет атрибуты, методы и исключения. Интерфейс связки содержит свойства, методы и обработчики событий. Свойства соответствуют атрибутам, обработчики - специализированным методам. Исключения также могут быть реализованы в связках, но XBL не имеет для этого явного синтаксиса. Чтобы создать исключение, используйте оператор throw в JavaScript.

    XBL поддерживает традиционные концепции объектно-ориентированного подхода. В таблице 15.1 приведено описание поддержки ОО в XBL.

    Таблица 15.1. Поддержка объектов в XBL

    КонцепцияПоддержка в XBL
    АгрегированиеТеги XBL собирают контент (включая контент граничного тега) и код JavaScript в единую связку, сливая эксплицитный и анонимный контент в единое целое
    ЛокализацияГраничный тег хранит логику всего контента и JavaScript. XBL-связка может содержать другой граничный тег, и, следовательно, следующую связку
    ИнкапсуляцияГраничный тег содержит всю полезную информацию о связке, хотя некоторая полезная информация может быть получена из JavaScript-объекта document
    НаследованиеXBL имеет атрибут inherits, для наследования XBL-атрибутов, и атрибут extends, для наследования целых связок
    Скрытие информацииКод подключенной связки скрыт и недоступен из граничного тега. Доступен только соответствующий интерфейс
    ИнтерфейсыСвязка, предоставляющая интерфейс, может быть наследована другой связкой с помощью атрибута extends. Связка может поддерживать интерфейсы XPCOM с помощью атрибута implements
    Позднее связываниеСвязки подгружаются в граничный документ независимо и асинхронно. Связка может быть подгружена и выгружена в любое время с помощью правил CSS2. На связки следует ссылаться конкретно, нет механизма определения исходной связки по производной от нее связке
    ОбъектностьГраничная связка с точки зрения JavaScript является объектом
    Объектно-ориентированностьАтрибут extends позволяет XBL-связке быть основанной на ряде (цепочке наследования) других связок. См. Последующее обсуждение
    Множественное наследованиеНе поддерживается
    Определение типовНе поддерживается. Можно использовать лишь интерфейс nsIClassInfo

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

    На рисунке 15.1 приведен пример цепочки наследования, которую имеет тег <button>.

    Иерархия наследования для XUL-тега <button>.


    Рис. 15.1.  Иерархия наследования для XUL-тега <button>.

    В примере показаны цепочки наследования трех элементов. Связка "button" привязана к тегу <button>. Эта связка - начало цепочки наследования. Две другие связки - это связки общего назначения, наследуемые во множестве мест в XUL. Связка button-base используется во всех (или почти всех) кнопкоподобных виджетах, а связка basetext используется во всех виджетах, имеющих дело с текстом. Связки button и button-base определены в одном и том же XBL-документе, (button.xml), а basetext в другом, general.xml. Все три связки добавляют некоторое содержание в DOM-объект тега <button>.

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

    В Таблице 15.2 описано, как наследуются различные свойства связок XBL.

    Таблица 15.2. Наследование свойств в XBL-связках

    Свойство XBLНаследуется?
    <resources>Все связки цепочки наследования загружают свои ресурсы. Все стилевые таблицы имеют по умолчанию одинаковый вес, но правила у начала цепочки подгружаются позже, и, следовательно, отменяют ранее загруженные правила
    <content>Нет наследования. Используется единственная связка из начала цепочки наследования. Если граничные теги содержатся в теге <content>, а не наследуются, используются их связки
    <field>, <property>, <method>Свойства и методы наследуются. Если имя составной связки совпадает с именем элементарной, составная замещает основную, и последняя становится недоступна из скриптов составной
    <constructor> и <destructor>Оба свойства наследуются. Конструкторы запускаются по одному, начиная с первой основной связки и заканчивая последней составной. Деструкторы также запускаются по одному в обратном порядке
    <handlers>Обработчики событий наследуются. Обработчики событий составной связки отменяют действие обработчика базовой связки, если речь идет об одном и том же событии
    Атрибут displayНет наследования. Используется атрибут последней составной связки

    15.1.6. Набор компонентов XBL

    Система XBL - это очень простой набор компонентов. Он позволяет стандартизированным компонентам взаимодействовать удобным образом.

    Компонентная система, подобная XBL - система модулей Perl, доступных с помощью ключевых слов use и require. Обе системы достаточно примитивны. К связкам XBL можно получить доступ через DOM, так же как к модулям Perl через символьные таблицы. Связки XBL не включаются в другие связки также прямолинейно, как модули Perl, но в обоих случаях это использование другого кода.

    Имена компонентов в системе XBL - это URLs в форме Resource#Id, где Resource - это web-адрес документа, а id - значение атрибута id тега <binding>. Так же как RDF, XBL понимает URL с id как ресурс, а не как сдвиг (offset) в существующем ресурсе.

    XBL не имеет регистра компонентов или их службы имен, и не имеет возможности доступа к внутреннему устройству связок. Вместо этого документ XUL или HTML имеет простой внутренний список активных связок, используемых данным документом.

    Компонентная система XBL является распределенной системой, поскольку связка может быть получена по URL откуда угодно. Связки не имеют доступа к серверу, с которого были загружены, если только не содержат кода именно для этой цели.

    Система XBL не имеет свойств, подобных сложным компонентным системам, таким как COM от Microsoft, XPCOM самой платформы Mozilla, OMG CORBA или JavaBeans фирмы Sun. Ее сила в простоте и ясности.

    15.1.6.1. Связки как компоненты XPCOM

    Связка XBL может реализовывать один или несколько интерфейсов XPCOM. Это позволяет объекту DOM граничного тега вести себя как компонентам XPCOM с этими интерфейсами.

    Связки не могут вести себя как компоненты XPCOM до тех пор, пока они не привязаны.

    15.2. Конструирование связки XBL

    Этот раздел описывает конкретные теги и атрибуты XBL.

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

    <bindings> <body> <children> <content> <image>
    

    Имена атрибутов, используемых в ином значении:

    method action command modifiers charcode keycode key name readonly 
    text
    

    15.2.1. Граничные теги и -moz-binding

    Большинство тегов XUL и HTML могут быть граничными в XBL. Чтобы тег мог быть граничным, он должен иметь фрейм - прямоугольную оформляемую стилями зону, в которой он высвечивается.

    Тег со следующим атрибутом не имеет фрейма и не может быть граничным.

    { display: none; }
    

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

    Связываются в связки конкретные теги, а не имена тегов. Каждое стилевое правило, содержащее -moz-binding, связывает набор конкретных тегов. В листинге 15.3 приведен ряд примеров.

    box         { -moz-binding: url("binding.xml#sample"); }
    #id         { -moz-binding: url("binding.xml#sample"); }
    .class        { -moz-binding: url("binding.xml#sample"); }
    box[X="here"]  { -moz-binding: url("binding.xml#sample"); } 
    box,vbox,hbox   { -moz-binding: url("binding.xml#sample"); }
    box vbox      { -moz-binding: url("binding.xml#sample"); }
    
    Листинг 15.3. Примеры селекторов CSS2 для связок.

    Первая строка кода присоединит каждый конкретный тег <box> к одной и той же связке. Каждый конкретный тег имеет собственный интерфейс и свое множество состояний. Вторая строка присоединит лишь один тег, независимо от его имени. Третья строка присоединит любой тег с данным атрибутом class. Четверная строка - подмножество тегов <box>, имеющих атрибут X="here". Пятая строка - теги <box>, <vbox>, <hbox> и т.д. Если два правила присоединяют один и тот же конкретный тег (а это очень неудачный вариант), используется более конкретное правило, либо более позднее правило, если селекторы одинаковы. То есть обычные правила выполнения инструкций CSS2.

    Такая обработка стилевых правил используется в XUL для тегов, имеющих различные варианты, наподобие тега <button>. Каждый вариант использует различные связки. Их можно посмотреть в файле xul.css архива toolkit.jar в chrome.

    Можно явно указать отсутствие связок:

    { -moz-binding : none; }
    

    Mozilla (по крайней мере, до версии 1.4) требует специальных приемов для добавления и уничтожения связок; следуйте советам, данным в разделе "Скриптинг", а не применяйте стилевые правила "в лоб".

    Наконец, заметим, что есть одна давно известная ошибка (по крайней мере до версии 1.4). Тег со следующим стилем не будет работать корректно:

    { display: block; }
    

    Потому что block не является частью XUL, это ловушка, используемая в HTML. Избегайте комбинировать этот стиль с -moz-binding.

    15.2.2. Тег <bindings>

    Тег <bindings> является верхним тегом в документе XBL. У него нет специального значения и специальных атрибутов. Он может содержать только теги <binding>.

    Декларация <!DOCTYPE> не обязательна для документов XBL. Если такая декларация есть, необходимым минимумом является следующее:

    <!DOCTYPE bindings>
    

    15.2.3. Тег <binding>

    Тег <binding> содержит описание одной XBL-связки. Он может иметь до четырех вложенных тегов. Каждый из следующих тегов может появиться максимум один раз:

    <resources> <content> <implementation> <handlers>
    

    Все четыре тега опциональны. Рекомендуется, чтобы они были описаны именно в данном порядке, но это не обязательно. Тег <binding> без контента бесполезен, за исключением того случая, когда он убирает весь контент и все действия по умолчанию из граничного тега. Эффект подобен действию стиля "-moz-binding : none".

    Тег <binding> имеет следующие специальные атрибуты:

    id display extends inheritstyle
    

    Атрибут id идентифицирует связку. Связка не имеет имени, а только идентифицирующий ее URL. Если часть #id в этом URL отсутствует, будет использован атрибут id первой связки в документе XBL.

    Атрибут extends - это механизм наследования в XBL. Его можно установить в URL следующей связки. Последовательность связок, определяемых атрибутом extends, формирует цепочку наследования; это обсуждалось в разделе "Объектные свойства связок".

    Атрибут extends может иметь сокращенное значение. Его можно использовать также для атрибута display. Оно состоит из префикса xul: и имени тега XUL. Оно утверждает, что связка основывается на существующем теге и должна использовать его свойства. В случае атрибута extends, этот тег дает базовую реализацию связки. Пример:

    extends="xul:box"
    

    Атрибут display может иметь только сокращенное значение, используемое для атрибута extends. Оно указывает, какой объект box применяется в данной связке. Указанный объект box - один из тех, которые обычно используются указанным тегом XUL. Пример:

    display="xul:button"
    

    Эта запись означает, что связка приобретет объект box, который имеет XUL тег <button>.

    Атрибут inheritstyle может иметь значение false. Его значение по умолчанию true. Если его установить в false, граничный тег не приобретет тех стилей, с помощью которых оформлялся до того, как к нему была присоединена связка.

    15.2.4. Тег <resources>

    В теге <resources> указывается, какие еще документы требуются данной связке. Он подобен HTML тегу <LINK>. Тег <resources> не имеет специальных атрибутов и может содержать только следующие теги:

    <image> <stylesheet>
    

    Можно указать ноль или более этих тегов. Последовательность не имеет значения, за исключением того, что более поздние теги <stylesheet> будут применены после более ранних. Система XBL обходится не слишком интеллектуально с содержанием тега <resources>, как описывается далее.

    15.2.4.1. Тег <image>

    Тег <image> идентичен одноименному XUL-тегу, кроме того, что здесь единственным используемым атрибутом является src. Так же, как в XUL, src определяет URL изображения. <image src="icons.gif"/>

    XBL ничего не делает с этим тегом, лишь предписывает платформе Mozilla получить это изображение и кэширует его. Это подсказка платформе, что данное изображение важно и может понадобиться позже. Оно может понадобиться в XUL теге <image>, HTML теге <img>, CSS2 свойстве liststyle-image, или в контентной части XBL-определения. Указание этого тега - лишь оптимизация выполнения.

    15.2.4.2. Тег <stylesheet>

    Тег <stylesheet> идентичен HTML тегу <style src=>. Он не может иметь иного собственного контента.

    <stylesheet src="chrome://global/skin/button.css"/>
    

    Стилевые таблицы используются в XBL, чтобы хранить все детали визуализации виджета. Это позволяет виджетам быть зависящими от темы. Это также дает возможность сконструировать стилевые правила так, чтобы они применялись лишь к анонимному контенту граничного тега. В предыдущем примере все визуальные детали тега <button> (основанного на XBL связке с id="button") сохранялись по зависящему от темы URL.

    Можно использовать более одного тега <stylesheet> на связку, но это делается редко. Если такое случается, стили прикладываются в порядке их появления.

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

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

    15.2.5. Тег <content>

    Тег <content> содержит весь анонимный контент связки. Он может включать XML теги из любого пространства имен XML, такого как HTML, XUL или MathML. Он может содержать также тег <children>, специфичный для XBL. Источники событий и наблюдатели XUL, как и тег <script>, не работают в анонимном контенте.

    Тег <content> имеет два специальных атрибута:

    includes excludes
    

    Оба атрибута считаются устаревшими, их следует избегать. Используйте вместо них в анонимном контенте тег <children>. Если в теге <content> появляются любые другие атрибуты, они будут скопированы в граничный тег.

    Атрибуты граничного тега могут быть добавлены тегам внутри тега <content> с помощью атрибута inherits. Эксплицитный контент граничного тега может быть слит с тегами внутри тега <content> с помощью тега <children>.

    Если связка использует атрибут extends, так что строится на основе другой связки, тег <content> в этой другой связке игнорируется.

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

    15.2.5.1. Объединение атрибутов с помощью xbl:inherits=

    Анонимный контент тега <content> может быть фрагментом документа любого типа и любого размера. Тег <children> может появиться в любом месте этого фрагмента.

    Отличные от XBL теги в контенте могут использовать один атрибут XBL, а именно inherits. Этот атрибут служит для того, чтобы передать параметры и их значения из граничного тега анонимному контенту, как если бы эти контентные теги были наблюдателями для граничного тега. Передача параметра осуществляется путем переименования и копирования значения атрибута граничного тега тегам анонимного контента.

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

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

    att1 
    att1=att2 
    xbl:text=att2 
    att1=xbl:text
    

    att1 и att2 - любые правильные имена атрибутов XML.

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

    Вторая форма записи говорит о том, что атрибут att1 анонимного тега должен иметь значение атрибута att2 граничного тега.

    Третья форма использует специальное зарезервированное имя xbl:text. Она говорит, что <текстовое> содержание анонимного тега (a DOM 1 Text Node) должно иметь значение атрибута att2 граничного тега. Это дает тот же эффект, что и использование тега <textnode> в шаблонах XUL.

    Четвертая форма говорит, что атрибут att1 анонимного тега должен получить значение эксплицитного контента граничного тега, который в этом случае должен быть текстовым содержанием, DOM 1 text node.

    Поскольку атрибут inherits является атрибутом XBL, он не может появиться в не-XBL теге. Он должен быть полностью определен, включая его пространство имен XML. Это достигается благодаря префиксу xbl: (или подобному) и добавлению полной декларации пространства имен в начале документа XML. В силу этих причин XBL документы часто имеют две ссылки на пространство имен XBL:

    <bindings id="test" 
    xmlns="http://www.mozilla.org/xbl" 
    xmlns:xbl="http://www.mozilla.org/xbl"
    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
    

    Первое пространство имен XBL обычное по умолчанию, второе указывается специально для атрибута inherits.

    Если все это соединить, получится следующий пример атрибута inherits:

    <xul:label dir="ltr"
    xbl:inherits="xbl:text=value,style,align=justify"/>
    

    Если граничный тег был таким:

    <mytag value="Test" style="color:red" justify="start"/>
    

    то после замены анонимный тег <xul:label> будет заменен на:

    <xul:label dir="ltr" style="color:red" align="start"> Test
    </xul:label>
    

    Атрибут inherits может быть использован в любом месте анонимного контента, и может связать один атрибут граничного тега со многими атрибутами тегов анонимного контента, если потребуется. Теги анонимного контента всегда могут определять атрибуты явно, как dir="ltr". Если специальный атрибут xbl:text использован более одного раза, все определенные значения появятся в текстовой ноде анонимного тега.

    15.2.5.2. Слияние тегов с помощью тега <children>

    Тег <children> управляет слиянием эксплицитного контента граничного тега (если он есть) с анонимным контентом (если он есть) тега <content>. Тег <children> может появиться в любом месте анонимного контента связки. Он представляет любой эксплицитный контент, который имеет граничный тег. Он имеет следующий специальный атрибут:

    includes
    

    Атрибут includes может содержать разделенный запятыми список имен тегов. Определения пространства имен не требуется. Этот атрибут изменяет процесс слияния контентов. Вот обзор возможностей:

    1. Если эксплицитного контента нет, любые теги <children> в анонимном контенте игнорируются. Это все равно что удалить все теги <children> из секции <content>, но оставить остальные теги нетронутыми. Финальный контент является копией оставшегося анонимного контента.
    2. Если анонимного контента нет, то граничный тег после слияния вовсе не будет иметь контента.
    3. Если есть и эксплицитный, и анонимный контент, подсчитываются все теги эксплицитного контента, являющиеся непосредственными дочерними тегами граничного тега. Каждому такому тегу должен соответствовать один тег <children>. Если это условие выполняется, финальным контентом будет объединенная копия эксплицитного и анонимного контентов.
    4. Если что-то из этих условий не выполняется, то либо теги <children> в анонимном контенте использовались некорректно, либо в граничном теге есть некоторый неучтенный эксплицитный контент. В этом случае результат объединения непредсказуем.

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

    Если у тега <children> нет атрибута includes, эксплицитным контентом будет замещен и сам тег <children>, и поддерево его контента в анонимном контенте. В этом случае должен быть ровно один тег <children>.

    Если атрибут includes есть, тег <children> будет замещен лишь частью эксплицитного контента. Будут размещены все непосредственные дочерние теги граничного тега, чьи имена совпадают со списком в атрибуте includes. Они будут помещены в той точке анонимного контента, где был данный тег <children>. Они будут размещены в том же порядке, в каком были в граничном теге.

    3. Если пункт 2 повторяется аккуратно, то окажется достаточно тегов <children includes=>, чтобы разместить весь эксплицитный контент. В этом случае весь эксплицитный и анонимный контенты будут слиты правильно.

    4. Если пункт 2 не повторяется достаточное количество раз, некоторые непосредственные дочерние теги граничного тега не найдут для себя соответствующего тега <children includes=>. Это неполный охват эксплицитных тегов. Mozilla сама добавит весь эксплицитный контент в финальный контент, а весь анонимный контент будет отброшен.

    Если пункт 2 реализован небрежно, некоторые дочерние теги граничного тега могут соответствовать более чем одному тегу <children>. Это непорядок, в таком случае результат может быть непредсказуем. В итоге разработчик связки должен либо (a) не делать никаких предположений об эксплицитном контенте граничного тега и избегать использовать includes, либо (b) полностью предвидеть, что граничный тег будет размещать в анонимном контенте и использовать includes везде; или (c) делать обоснованные догадки и положиться на то, что система XBL придумает здесь что-то более или менее рациональное.

    Листинг 15.4 демонстрирует корректный пример этой системы в действии.

    <!-- tag to be bound --> 
    <mytag> 
      <image src="face.png"/> 
        <description label="Smile"/> 
      <image src="face.png"/> 
    </mytag> 
    
    <!-- Example 1: unknown explicit content --> 
    <content> 
      <box> 
        <children/> 
      </box>
    </content>
    
    <!-- Example 2: known explicit content --> 
    <content> 
      <children includes="description"/> 
      <box> 
        <children includes="image"/> 
      </box>
    </content> 
    
    <!-- Example 3: known but optional explicit content -->
    <content> 
      <children includes="image|description"> 
        <label value="No emoticon supplied"/> 
      </children> 
    </content>
    
    Листинг 15.4. Исходный код примера объединения контента XBL.

    В данном листинге тег <mytag> - граничный. Остальные фрагменты принадлежат трем разным связкам. Пример 1 добавляет весь, неважно какой, эксплицитный контент. Пример 2 корректно добавит каждый тип эксплицитного контента в соответствующем месте. Пример 3 также добавит эксплицитный контент, но если его нет, вместо него будет использоваться тег <label>. В листинге 15.5. приведен финальный контент для всех этих случаев.

    <!-- Example 1: unknown explicit content --> 
    <mytag> 
      <box> 
        <image src="face.png"/> 
          <description label="Smile"/> 
        <image src="face.png"/>
      </box> 
    </mytag> 
    
    <!-- Example 2: known explicit content --> 
    
    <mytag>
      <description label="Smile"/> 
      <box> 
        <image src="face.png"/> 
        <image src="face.png"/> 
      </box> 
    </mytag> 
    
    <!-- Example 3: known but optional explicit content --> 
    <mytag> 
      <image src="face.png"/> 
        <description label="Smile"/> 
      <image src="face.png"/> 
    </mytag>
    
    Листинг 15.5. Сгенерированный контент для примеров объединения контентов.

    Если исходный контент тега <mytag> сильно изменить, пример 1 останется работающим, в то время как 2 и 3 могут дать неожиданные результаты.

    В примере листинга 15.6 приведена конструкция, которая никогда не будет работать надежно, потому что теги <image> эксплицитного контента не соответствуют ни одному из двух тегов контента анонимного.

    <content> 
      <children/> 
        <box> 
          <children includes="image"/> 
        </box>
    </content>
    
    Листинг 15.6. Плохо сконструированный контент для связки XBL.
    15.2.5.3. Слияние граничных тегов

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

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

    Можно сконструировать циклы вложения, но этого следует избегать. Тег <content> не должен содержать свой собственный граничный тег.

    15.2.6. Тег <implementation>

    Тег <implementation> - это простой тег-контейнер. Он реализует объектоподобный интерфейс связки. Этот интерфейс реализуется на JavaScript. Впоследствии он будет доступен скриптам JavaScript, работающим вне граничного тега. Созданный интерфейс доступен, как свойства и методы граничного тега.

    Тег <implementation> имеет один специальный атрибут:

    implements
    

    Этому атрибуту можно присвоить список (разделенный пробелами или запятыми) имен интерфейсов XPCOM, таких как nsISimpleEnumerator или nsIDOMEventListener. Может быть использовано любое имя интерфейса. Платформа Mozilla посчитает связку как соединенный через XPConnect компонент XPCOM и использует эти имена, чтобы определить, что связка должна делать. Тогда уж заботой разработчика связки будет, чтобы она выполняла то, что обещает. Система XBL автоматически добавляет функциональность, эквивалентную интерфейсу nsISupports.

    Если любой из этих интерфейсов реализован и объявлен, они появляются как интерфейсы граничного тега.

    Атрибут type не поддерживается тегом <implementation> или его подтегами. Подразумевается, что используется язык JavaScript. Рекомендуется задействовать синтаксис XML <![CDATA[ ]]>, если ваши скрипты имеют заметный размер.

    Тег <implementation> может содержать в качестве дочерних любые из следующих тегов, в любом порядке:

    <field> <property> <method>
    

    Тег <implementation> также может содержать один из следующих тегов:

    <constructor> <destructor>
    

    Все эти пять тегов содержат скрипты на языке JavaScript. Эти скрипты имеют доступ к некоторым хорошо известным свойствам JavaScript:

    this window document event
    

    Свойства window и document - те же самые, что используются в XUL и HTML и относятся к свойствам window и document граничного тега. Свойство this относится к объекту DOM Element object граничного тега, который имеет много полезных DOM-методов, как getAttribute(). Свойство event существует только для тега <handler> и содержит объект DOM Event object.

    Имейте в виду, что в секции <implementation> свойство this имеет особенность. Все простые значения, присваиваемые свойствам объекта this, автоматически конвертируются в тип "строка". Согласно этому правилу, следующий код вернет строку "1213":

    this.dozen = 12; alert(dozen + 13);
    

    Это преобразование заметно только при использовании оператора "+". В других математических операторах строки будут автоматически конвертированы в числа, и мы не заметим преобразования в строку. Простая подсказка - храните значения в объектах. Следующая строка кода вернет 25:

    this.d = {}; d.dozen = 12; alert(d.dozen + 13);
    

    Связка без тега <implementation> также полезна. Она может отображать контент и обрабатывать события.

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

    <implementation> 
      <field name="rating" readonly="true">60</field>
      <field name="lit">true</field> 
      <field name="age">0</field> 
      <property name="power" 
        onget="age++; return rating-age/1000;"
        onset="age=(rating-val)*1000;" >
      <method name="toggle"> 
        <body> 
        if ( this.power > 30 ) this.lit = !this.lit; 
        </body> 
      </method> 
    </implementation>
    
    Листинг 15.7. Пример интерфейса объекта, использующего XBL <implementation>.

    Этот объект имеет четыре свойства: rating (значение мощности, здесь, видимо, в ваттах), lit (булева константа) age (целое) и power (динамически вычисляемое значение). Он имеет один метод: toggle(). Имена свойств появляются и в значениях атрибутов XBL-тегов, и в коде JavaScript. Например, метод toggle() использует свойства power и lit, чьи имена также появляются как значения атрибута name тега <field>.

    Интерфейс в листинге 15.7 эквивалентен объекту JavaScript в листинге 15.8.

    var bulb = { 
      const rating : 60, 
      lit : true, 
      age : 0, 
      get power() { age++; return rating-age/1000; }, 
      set power(val) { age=(rating-val)*1000; }, 
      toggle : function () { 
         if ( this.power > 30 ) this.lit = !this.lit; 
      } 
    };
    
    Листинг 15.8. Объект JavaScript, эквивалентный листингу 15.7.

    Очевидно, оба листинга очень похожи. Почему же не использовать простой объект JavaScript и не отказаться от XBL? Такой объект придется вручную указывать для каждого тега, где это будет нужно, используя скрипты. При использовании XBL объект появится автоматически везде, где ему будет предписано появиться правилами CSS связки XBL.

    15.2.6.1. Тег <field>

    Тег <field> - самая простая часть интерфейса связки. Он предназначен для сохранения значения простой переменной. Используется для хранения информации о состоянии, указываемой программистом, для каждого граничного тега. Тег <property> - более гибкая версия тега <field>.

    <field> имеет два специальных атрибута:

    name readonly
    

    Атрибут name хранит имя свойства JavaScript. Это должно быть правильное значение имени переменной JavaScript.

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

    <field> имеет следующий синтаксис:

    <field>JavaScript expression</field>
    

    Обратите внимание, что содержание тега есть выражение, а не утверждение (statement). Поскольку выражение вычисляется лишь один раз, обычно это простое выражение, но оно может быть, если потребуется, и сложным. Примеры:

    <field name="count">3+2</field> 
    <field name="address" readonly="true">"12 High St"</field> 
    <field name="phonelist">[]</field>
    

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

    Однажды созданные теги fields являются свойствами объекта граничного тега, (т.е. то же самое, что this), и их далее можно использовать обычным образом.

    15.2.6.2. Теги <property>, <getter>, and <setter>

    Тег <property> предназначен для сохранения простой переменной, как и тег <field>. Эта переменная доступна как свойство JavaScript объекта граничного тега.

    Разница между <property> и <field> состоит в том, что переменная тега <property> ведет себя как интерфейс, в то время как переменная тега <field> - как простое значение.

    Значение переменной <property> может динамически вычисляться, когда она читается или записывается, и обе операции могут иметь вторичные эффекты. Это значит, что скрипт, использующий property, может вызывать иные процессы, получая или устанавливая ее значение.

    Вспомним лекцию 5, "Сценарии". Свойство property JavaScript может быть определено с функциями __defineGetter__ и __defineSetter__.

    Тег <property> работает точно таким же образом, за исключением того, что свойство property прописывается здесь тегами <getter> и <setter> или сокращенно, атрибутами onget и onset.

    Тег <property> имеет четыре специальных атрибута:

    name readonly onget onset
    

    name - имя свойства JavaScript, которое это свойство определяет. Оно должно быть правильным именем JavaScript.

    readonly означает, что property ведет себя как неизменяемая переменная JavaScript. Переменная может быть прочитана, но не изменена.

    onget - сокращенная запись тега <getter>, она содержит в качестве значения скрипт. Если присутствуют и onget, и <getter>, используется onget.

    onset - сокращенная запись тега <setter>, она содержит в качестве значения скрипт. Если присутствуют и onset, и <setter>, используется onset. onset бесполезен, если указано readonly="true".

    Тег <property> может иметь ноль или более тегов <getter> и <setter>. Контент этих тегов - функции JavaScript.

    Тег <getter> и атрибут onget должны содержать последовательность сообщений JavaScript. Возвращается результат этой последовательности.

    Тег <setter> и атрибут onset также должны содержать последовательность сообщений JavaScript, но возвращается значение переменной val. Специальная переменная val содержит устанавливаемое значение.

    В листинге 15.7 приведен простой пример onget и onset. Эквивалентный синтаксис с тегами <getter> и <setter> приведен в листинге 15.9. Секцию CDATA в данном примере можно не использовать, поскольку код тривиален. Повсюду, для ясности, применяется ключевое слово this - это рекомендуемая практика.

    <property name="power"> 
      <getter>
        <![CDATA[ this.age++; return this.rating - this.age/1000; ]]> 
      </getter> 
      <setter>
        <![CDATA[ this.age = (this.rating - val) * 1000; return val; ]]> 
      </setter> 
    </property>
    
    Листинг 15.9. Пример использования тегов <getter> и <setter>.

    Если <setter> и onset не определены, и делается попытка присвоить значение с помощью этого свойства, на консоли JavaScript появится сообщение об ошибке (или генерируется исключение). Если предпринимается попытка получить значение данного свойства, когда ни <getter>, ни onget не определены, будет возвращено значение undefined.

    15.2.6.3. Теги <method>, <parameter>, and <body>

    Тег <method> используется для определения метода интерфейса. Он содержит ноль или более тегов <parameter>, за которыми следует в точности один тег <body>. Тег <method> имеет один специальный атрибут:

    name
    

    Атрибут name указывает имя свойства JavaScript, содержащего объект Function, так что оно должно быть правильным идентификатором JavaScript. Тег <method> не поддерживает атрибут action, и Mozilla может упасть, если его использовать.

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

    Тег <parameter> имеет тот же синтаксис, что и тег <method>. Его единственный атрибут name определяет одно имя переменной, передаваемой методу. Порядок тегов <parameter> тот же самый, что и порядок передаваемых переменных.

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

    Невозможно создать два метода с одним именем, но разным числом параметров. Но поскольку все функции JavaScript поддерживают списки аргументов, нет нужды такие вариации создавать.

    Наконец, тег <body> содержит те сообщения JavaScript, которые образуют метод. Этот тег не имеет специальных атрибутов. Если метод должен что-то возвращать, следует использовать сообщение return. Объект arguments, определенный для всех функций JavaScript, также можно использовать в содержании тега <body>.

    Листинг 15.10 демонстрирует, как эти теги работают вместе:

    <method name="play"> 
      <parameter name="boy"/> 
      <parameter name="dog"/>
      <parameter name="ball"/> 
      <body>
        <![CDATA[ 
        if ( arguments.length != 3) 
          throw Components.results.NS_ERROR_INVALID_ARG; 
        if ( boy == "Tom" && dog == "Spot" ) { 
          return document.fetch(ball.type); 
        } 
        return null; 
        ]]>
      </body> 
    </method>
    
    Листинг 15.10. Пример тегов <method>,<parameter>, и <body>.

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

    function play(boy, dog, ball) { 
      if ( arguments.length != 3) 
      throw Components.results.NS_ERROR_INVALID_ARG; 
      if ( boy == "Tom" && dog == "Spot" ) { 
        return document.fetch(ball.type); 
      } 
      return null; 
    }
    
    Листинг 15.11. Функция JavaScript, эквивалентная тегу <method>.

    Эти два листинга даже трудно различить, так что неудивительно, что содержание тега <method> конвертируется в JavaScript, как только документ XBL загружается и парсится.

    15.2.6.4. Теги <constructor> и <destructor>

    Теги <constructor> и <destructor> - это обработчики событий, которые запускаются лишь однажды за время жизни связки. Они используются для инициализации и очистки, как и все конструкторы и деструкторы во всех объектно-ориентированных языках. XBL-конструкторы и деструкторы имеют стандартную объектно- ориентированную семантику и не следуют системе прототипов JavaScript.

    Они имеют один специальный атрибут:

    action
    

    Атрибут action - сокращение для содержания тегов <constructor> и <destructor>. Это содержание - тело функции JavaScript, так же как в XBL теге <body>. Его можно поместить между открывающим и закрывающим тегами, а можно использовать атрибут action. Если используются оба способа, применяется атрибут action, хотя так делать вряд ли имеет смысл.

    Тег <constructor> выполняет свою работу, когда связка объединяется с граничным тегом. Это может быть очень большое число раз, и если так, то нужно проследить, чтобы действие конструктора имело оптимальный код. Невозможно управлять связкой в цепочке наследования, используя этот тег. Нет также возможности управлять каким-либо граничным тегом в секции <content> из тега <constructor>. Коду конструктора аргументы не передаются, но указатель this всегда доступен. Код конструктора не обязан возвращать какое-либо значение.

    Если текущая связка наследует иную связку, то теги <constructor> запускаются в порядке от конца к началу в цепочке наследования, другими словами, тег <constructor> первичной связки будет в последним.

    Тег <destructor> выполняет свое действие, когда связка отсоединяется от граничного тега. Редко когда это случается без того, чтобы весь целевой документ не был закрыт, но если связка управляется вручную, или поддерживается состояние, разделяемое многими окнами, тег <destructor> может быть полезен. Так же, как и для тега <constructor>, здесь нет возможности воздействовать на связки, являющиеся частями текущей связки. Коду деструктора аргументы не передаются, и он не обязан возвращать какое-либо значение.

    Если текущая связка расширяет (наследуется из) другой связки, то теги <destructor> запускаются в порядке от начала к концу в цепочке наследования - другими словами, тег <destructor> первичной связки будет в последовательности первым.

    Обычная практика - создавать объекты XPCOM из конструктора. Если требуется лишь один такой объект для данной связки, а не по объекту на каждый граничный тег, то обычный способ устроить это показан в листинге 15.12.

    <constructor>
      <![CDATA[ if (!document.globalPicker) { 
        var Cc = Components.classes; 
        var Ci = Components.interfaces; 
        var comp = Cc["@mozilla.org/filepicker;1"]; 
        document.globalPicker = comp.getService(Ci.nsIFilePicker); 
      } 
      ]]>
    </constructor>
    
    Листинг 15.12. Создание глобальных компонентов с использованием XBL-конструктора.

    В этом коде каждый граничный тег проверяет, была ли уже проведена инициализация, и если была, просто игнорирует этот шаг. Все связки объединяются в разное время, но поскольку Mozilla выполняет лишь одну задачу одновременно (single-threaded), два конструктора не могут выполняться в одно и то же время.

    15.2.7. Тег <handlers>

    Тег <handlers> - простой контейнер, содержащий все обработчики событий XBL-связки. Он не имеет собственных специальных атрибутов. Он содержит один или более тегов <handler>. Каждый тег handler отвечает за исполнение некоторого кода JavaScript в ответ на событие DOM 2 граничного тега.

    Связки XBL в директории chrome содержат более 500 разных обработчиков событий. Множество обработчиков событий в стандартных приложениях - результат действия связок XBL, а не специального кода.

    Обработчики событий, определяемые в XBL, - один из четырех источников обработчиков событий в Mozilla. глава 6, "События", описывает поддержку обработчиков в стиле "on...". Там также описывается тег <key>. Его синтаксис очень похож на синтаксис тега <handler>. Последний тип обработчиков зарыт в самой глубине платформы. Эти обработчики написаны на C/C++ и автоматически устанавливаются платформой при первом открытии окна.

    Этот последний тип обработчиков - обработчики по умолчанию, которые действуют, если ничто иное не установлено. Платформа Mozilla создает специальный контроллер команд для обработки фокуса и точно так же устанавливает ряд специальных обработчиков для наиболее типичных событий. Когда добавляются обработчики <key>, они пристраиваются на верхушку группы фундаментальных обработчиков, так же, как определенные в приложении обработчики onclick пристраиваются на верхушку XBL-связок. Когда добавляются обработчики <handler>, они располагаются рядом с фундаментальными обработчиками, как если бы и те, и другие были зарегистрированы с помощью addEventListener().

    Хотя последний, наиболее фундаментальный тип обработчиков реализован как часть XBL системы, они недоступны для прикладных программистов. Главное, на что следует обратить внимание, это то, что есть обработчики, лежащие ниже XBL системы, и поиск в chrome не обнаружит кода, ответственного за них.

    15.2.7.1. Тег <handler>

    Тег <handler> очень похож на XUL-тег <key>. Он определяет одну цель события (event target) и одно действие для одного точно определенного события. Действие - либо фрагмент скрипта, либо команда.

    Тег <handler> реализует действие по умолчанию для события, возникающего в граничном теге. Оно может быть переопределено в самом граничном теге с помощью обычного обработчика событий DOM 2.

    Чтобы обработчик начал действовать, в граничном теге должно возникнуть соответствующее событие. Это значит, что событие должно быть создано либо действием пользователя, как последовательность изменений документа, либо с помощью метода DOM 2 dispatchEvent(). Обсуждение событий XBL следует ниже, в разделе "Скриптинг" этой главы.

    Тег <handler> имеет следующие специальные атрибуты:

    event phase command modifiers clickcount key keycode charcode 
    button action
    

    Атрибут event определяет имя события для обработчика, из тех событий, которые поддерживает Mozilla. В Таблице 6.1 приведен список имен событий. Например, событие mousedown - пример события, которое не имеет значения по умолчанию.

    Атрибут phase определяет, на какой стадии события в цикле DOM 2 Event действие должно начинаться. Он может иметь значения "capturing", "target" или "bubbling". По-умолчанию "bubbling".

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

    Атрибут modifiers имеет список значений, разделенный пробелами или запятыми, а именно shift, control, alt, meta, accel и access. Он используется, чтобы определить, какие модификаторы клавиш должны быть нажаты, чтобы клавиатурное событие имело место. Все указанные модификаторы должны быть нажаты. accel - общий, платформеннонезависимый модификатор, описываемый в главе 6, "События". access соответствует случаю, когда нажимается "горячая клавиша". Горячие клавиши определяются XUL-тегом key= attribute. По умолчанию модификаторов клавиш нет.

    Атрибут clickcount используется только для событий мыши. Он содержит единственную цифру, определяющую, сколько кликов мышью требуется (движение вниз-вверх клавишей мыши - один клик). Максимальное значение определяется операционной системой. Mozilla поддерживает, по крайней мере, три клика. Значения по умолчанию нет.

    Атрибут key используется только для клавиатурных событий. Он содержит единственный печатаемый символ, например "A". Нужно использовать либо key, либо keycode для клавиатурных событий, но не оба атрибута вместе. Значения по умолчанию нет.

    Атрибут keycode используется только для клавиатурных событий. Он содержит единственный код VK_ из списка в Таблице 6.3. Нужно использовать либо key, либо keycode для клавиатурных событий, но не оба атрибута вместе. Значения по умолчанию нет.

    Атрибут charcode - устаревшее название атрибута key. Используйте key.

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

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

    Если не определены ни command, ни action, можно пометить обработчик событий на JavaScript между началом и концом тега <handler>. Вот пример:

    <handler event="click" phase="target" clickcount="1" button="0">
    this.do_custom_click(); // a <method> of the binding 
    </handler>
    

    Отныне это будет действием по умолчанию для события: "единичный клик левой кнопкой мыши". Метод do_custom_click() определен где-то в связке, так что он может быть вызван прямо в DOM-объекте граничного тега.

    15.2.8. Неиспользуемые теги и атрибуты

    Система XBL имеет ресурс тегов, которых нет, но кажется, что они существуют. Следующее описание применимо к версиям до 1.4.

    Атрибут type для тегов <bindings>, <method> и иных.

    Атрибут applyauthorsheets тега <content>.

    Атрибут applybindingsheets тега <children>.

    Тег <element> и все ссылающиеся на него.

    Следующие события не поддерживаются:

    contentgenerated, contentdestroyed, bindingattached, 
    bindingdetached.
    

    Следующие два атрибута сохранились от ранних версий XBL и их следует избегать: атрибут includes при использовании с тегом <content> и атрибут charcode при использовании с тегом <handler>.

    Тег <script> не является частью XBL и в чистом XBL работать не будет.

    Следующие атрибуты ничего не делают:

    attachto applyauthorstyles styleexplicitcontent
    

    15.3. Комбинируя несколько связок

    Система наследования XBL была рассмотрена ранее в разделе "Объектоподобные свойства связок" ранее в этой главе. Здесь мы рассмотрим ее использование. Только создатель связки может использовать наследуемые связки.

    15.3.1. Простое наследование с extends=

    Листинг 15.13 показывает связки, связанные наследованием.

    <!-- both bindings contained in example.xml --> 
    
    <binding id="smileyA">
      <content> 
        <xul:image src="faceA.png"/> 
      </content> 
      <implementation>
        <method name="methodA"> 
          <body> return true; 
          </body> 
        </method>
      </implementation> 
    </binding> 
    
    <binding id="smileyB" extends="example.xml#smileyA"> 
      <content> 
        <xul:image src="faceB.png"/>
      </content> 
      <implementation> 
        <method name="methodB"> 
          <body> return false; 
          </body> 
        </method> 
      </implementation> 
    </binding>
    
    Листинг 15.13. Простейший пример наследования связки.

    В таблице 15.2 описывается, как наследуются различные части связки.

    Если связка smileyA соединяется с XUL-тегом, этот тег будет иметь в качестве контента лишь тег <image src="faceA.png">, и один метод, methodA(). В этом случае цепочка наследования граничного тега имеет длину 1 и содержит лишь связку smileyA. Здесь нет наследования, и smileyA - первичная связка.

    Если с XUL-тегом связывается smileyB, то этот тег будет иметь в качестве контента единственный тег <image src="faceB.png">, и два метода, methodA() и methodB(). В этом случае цепочка наследования граничного тега имеет длину 1 и содержит связку smileyB в начале и связку smileyA в хвосте цепочки. В этом случае наследование есть, и smileyB - первичная связка.

    Обе связки можно применять к разным тегам в одно и то же время.

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

    15.3.2. Связки без контента

    Популярный прием, часто используемый в Mozilla - использовать логику набора связок в связке без содержания. Такая связка не имеет секций <resources> или <content>, и никогда (или редко) непосредственно связывается с тегом. Она содержит главным образом программную логику и информацию о состоянии.

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

    Примером такой связки с нулевым контентом может быть связка button-base в файле button.xml архива toolkit.jar в the chrome. Она содержит:

    properties: accessible type dlgType group open checked check-
    State autoCheck 
    handlers: event="command"
    

    Эти свойства и события отвечают за управление рядом состояний, используемых кнопкоподобными виджетами. Например, свойство checked имеет скриптовые логики <setter> и <getter>, которые позволяют граничному тегу иметь <checkbox> или <radio>-подобные состояния. Эти логики выполняют домашнюю работу в граничном теге, устанавливая атрибуты XML и координируя состояние checked с другими возможными атрибутами.

    Логика в такой связке наследуется многими другими связками. Эти связки, в свою очередь, наследуются многими тегами XUL: всеми разновидностями <button>, всеми вариантами <toolbarbutton>, <thumb>, <dropmarker>, <radio>, <menu>, кнопками в <wizard> и <dialog>-подобными окнами и т.д. Ясно, что связка button-base используется очень широко.

    Теги, основанные на связках с нулевым контентом, позволяют избежать ненужного удвоения кода. Когда некоторый тег использует такую связку, он должен иметь секцию <content>.

    15.3.3. Связки, обеспечивающие ввод данных

    Можно создать связку XBL для обработки пользовательского ввода. Такая связка имеет большую секцию <handlers> и понемногу всего остального, хотя может иметь также методы и свойства.

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

    <binding id="click-facade" extends="bindings.xml#base-widget">
      <handlers> 
        <handler event="click"> this.click(); </handler>
      </handlers> 
    </binding>
    

    Эта простая связка возводит фасад ввода между вводом единичного клика и виджетом. Она не обрабатывает метод click() - эта задача остается для некоторой иной связки. Данный метод может быть стандартным (в этом случае его обеспечивает связка base-widget), или высокоспециализированным (в этом случае его обеспечит связка, расширяющая связку click-facade).

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

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

    В Платформе Mozilla (до версии 1.4) нет фасадов ввода, но есть простор для их применения. Если вы заглянете в связки XBL для тегов <tree>, <listbox>, <tabbox> и HTML <select>, то увидите одинаковое поведение при обработке клавиатурного ввода во всех этих связках. Это подмножество кода может быть объединено в фасадах ввода и затем использоваться в любом виджете, обеспечивая однотипное поведение.

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

    15.3.4. Связки внутреннего контента

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

    Связки внутреннего контента создавать просто. Нужно сделать некоторые предположения о родительском теге граничного тега такой связки. Мы предполагаем, что граничный тег будет предоставлять контент своему родительскому тегу, который должен являться контейнером. Контейнер отвечает за границы виджета. Связка внутреннего контента полагается на то, что родительский тег будет работать корректно. В XUL очевидными контейнерами являются <box>, <button>, <menupopup>, <scrollbar> и <toolbar>.

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

    В XUL несколько связок следуют этой философии. Например, теги, используемые внутри тега <scrollbar>: <thumb>, <slider> и <scrollbarbutton>. Не все эти теги имеют XBL-связки, но все они полагаются на родительский тег для корректного функционирования. Примеры тегов с XBL связками: тег <dropmarker> внутри тега <button> и <tabs> внутри <tabbox>.

    15.4. Как протекает процесс связывания

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

    • При загрузке целевого документа верстальная машина Mozilla обнаруживает необходимость связки, заметив стиль -moz-binding у некоторого загружаемого тега.
    • У каждого документа есть список еще не выполненных шагов. Этот список увеличивается на единицу. Пока список дел не выполнен, в целевом документе не возникнет событие onload.
    • Если полная законченная копия связки еще не существует для целевого документа, выполняются шаги 4-7. Если существует, процесс продолжается с пункта 8.
    • XBL документ, описывающий связку, извлекается и заносится в кэш Mozilla. Это делается параллельно с загрузкой целевого документа.
    • Извлеченный XBL документ парсится и компилируется во внутреннюю структуру данных.
    • Если присутствует атрибут extends, шаги 2-7 повторяются (параллельно) для каждой связки в цепочке наследования. Так что какая-то другая связка может достичь шага 7 до первичной связки.
    • Скомпилированная связка добавляется в менеджер связок документа. Это XPCOM-компонент, содержащий список всех связок документа.
    • Теперь доступна, то есть загружена и скомпилирована, вся цепочка наследования. Целевой тег документа теперь связан со связкой - по крайней мере, в форме внутреннего указателя в Mozilla. До этого шага скрипт, инспектирующий граничный тег, связки не обнаружит. После этого шага скрипт тоже не сможет инспектировать граничный тег, пока не будут выполнены все оставшиеся шаги. Потому что оставшиеся шаги займут единственную нитку вычислений в Mozilla, пока все они не будут выполнены.
    • Атрибуты тега <content> копируются в граничный тег.
    • Эксплицитный и анонимный контенты сливаются, а результат копируется в граничный тег с помощью операций DOM.
    • Обработчики <handler> устанавливаются в граничном теге с помощью менеджера связывания.
    • Свойства <property> и <method> добавляются DOM-объекту граничного тега.
    • Запускается каждый код <constructor> в цепочке наследования.
    • Если, как результат этих шагов, возникают некоторые изменения в стилевых таблицах, они выполняются как последний шаг.
    • Список намеченных к выполнению перед запуском события onload задач уменьшается на единицу.

    15.5. Скриптинг

    Для JavaScript граничный тег выглядит как обычный объект DOM. Скрипты, выполняемые вне граничного тега, могут воздействовать на его DOM-объект обычными способами:

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

    Свойства могут быть добавлены или установлены в null. Свойства, содержащие методы (объекты Function), могут быть изменены определенными нами методами.

    Обработчики событий, загруженные с помощью стилевого атрибута on..., или с помощью DOM 2 метода addEventListener(), могут заменить собой обработчики связки.

    Интерфейсы DOM 1 могут модифицировать атрибуты граничного тега.

    Интерфейсы DOM 1 могут модифицировать финальный объединенный контент граничного тега.

    Ни одно из этих действий не затрагивает XBL документ связки для граничного тега.

    Программисту XBL связки следует в первую очередь обратить внимание на время загрузки связки. Общего способа определить, закончился ли процесс загрузки связки, нет. Единственный способ удостовериться в этом - использовать обработчик onload документа, к которому принадлежит данный тег, и избегать шаблонов с отложенной ("ленивой") загрузкой (lazy-loading templates). Если вы используете целиком вручную написанные связки, снабдите их некоторым сигналом, что загрузка окончена.

    Другой важный аспект скриптования граничного тега имеет дело с обработкой событий. Это материя обычных DOM событий и конкретного состояния фокуса ввода.

    Обычно события DOM распространяются вглубь финального объединенного контента граничного тега, как если бы это был обычный XUL-тег. С точки зрения специалиста по прикладному программированию, граничный тег - единственный видимый элемент и последний шаг в фазе захвата события. А с точки зрения создателя связки, активную роль играет контент граничного тега. Здесь событие продолжает распространяться вглубь связки и может быть доступно с помощью объекта event. Свойство event.originalTarget этого объекта очень часто используется в связке; (в версии 1.4 свойство event.target еще не надежно).

    Если, однако, граничный тег не является частью фокусного кольца данного окна (как, например, тег XUL <box>), часть объединяемого контента все еще может получить событие. В частности, если объединяемый контент содержит теги, являющиеся кандидатами в фокусное кольцо (как <checkbox> или <button>), эти теги могут получить фокус клавишей tab. Здесь работает следующий принцип:

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

    Система связок XBL не имеет интерфейса для "обновления" или "перестройки", которым можно было бы обновить граничный тег. Граничный тег может только целиком получить или потерять связку. Существует два способа сделать это.

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

    .binding-installed { -moz-binding : url("test.xml#Test"); }
    .binding-removed { -moz-binding : none; }
    

    Вместо того чтобы указывать значение самому свойству -moz-binding, присвойте атрибуту класса граничного тега одно из этих двух значений. Это работает корректно.

    Второй путь изменить связку граничного тега - использовать специальный интерфейс XPCOM. Этот интерфейс

    nsIDOMDocumentXBL
    

    Он всегда доступен для объекта документа. В Таблице 15.3 перечислены методы этого интерфейса.

    Таблица 15.3. Интерфейс nsIDOMDocumentXBL

    ВозвращаетСигнатура метода
    voidaddBinding(nsIDOMElement element, String URL)
    voidremoveBinding(nsIDOMElement element, String URL)
    nsIDocumentloadBindingDocument(String URL)
    nsIDomElementgetBindingParent(nsIDOMNode node)
    nsIDOMNodeListgetAnonymousNodes(nsIDOMElement element)
    nsIDOMElementgetAnonymousElementByAttribute(nsIDOMElement element, String attribute, String value)

    Методы addBinding() и removeBinding(), соответственно, добавляют и убирают из объекта DOM граничного тега первичную связку. Если первичная связка имеет цепочку наследования, она присоединяется вся. Эти два метода выполняются с синхронизацией, т.е. не возвращают результата до тех пор, пока изменение связки не выполнено целиком.

    Метод loadBindingDocument() загружает (синхронизируя процесс) XBL-документ с указанным URL в дерево DOM, которое является возвращаемым значением. Этот метод можно использовать для исследования структуры связки. Это полезно при создании таких инструментов, как дизайнеры и инспекторы DOM. Это также способ изменения связки с синхронизацией процесса - то есть изменения связки так, что выполняемый скрипт тормозится до тех пор, пока новая связка не окажется полностью на своем месте.

    Оставшиеся два метода - XBL-вариации метода document.getElementById(). Вместо того, чтобы возвращать объекты тега из дерева DOM документа, они возвращают объекты тега из анонимного контента XBL-связки. Это единственная часть спецификации XBL-связки, доступная из скрипта. В обоих методах передаваемый элемент DOM - объект DOM граничного тега.

    getAnonymousNodes() возвращает объект, содержащий все непосредственные дочерние теги тега <content> связки. Число дочерних тегов равно свойству length возвращаемого объекта, и они доступны методом item() возвращаемого объекта, то есть номеру индекса.

    getAnonymousElementByAttribute() возвращает единичный тег из набора тегов внутри тега <content> связки.

    Возвращаемый тег имеет атрибут name и атрибут value, указанные в вызове метода.

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

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

    Система XPCOM имеет два компонента, имеющих отношение к XBL:

    @mozilla.org/xbl;1 @mozilla.org/xbl/binding-manager;1
    

    Ни один из них не имеет интерфейсов, пригодных для использования в прикладных скриптах.

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

    Расширение -moz-binding стилей CSS2 - единственная стилевая опция XBL. Об этом рассказано в разделе "Граничные теги и -moz-binding".

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

    15.7. Практика: Тег <noteplacer>

    В данном разделе "Практика" мы разработаем некоторый код общего назначения, использующий XBL. В частности, мы создадим новую XBL-связку и соответствующий ей XUL-тег, которые внесут свой вклад в содержание и поведение диалогового окна утилиты NoteTaker.

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

    В утилите NoteTaker нет повторяющихся частей кода в контенте панели инструментов или диалогового окна, так что вряд ли стоит использовать XBL. Но ведь нам нужен пример для иллюстрации этой технологии. Стоит поэкспериментировать с информацией о позиционировании панели Edit (параметры top, left, height, width). Эти четыре параметра очень похожи на стили позиционирования в стандарте CSS2, и это может потом пригодиться в других приложениях. Более того, существующая реализация позиционирования несколько неуклюжа: пользователь вынужден гадать, вводя значения, а никакого отклика на вводимые данные нет до закрытия диалогового окна, а тогда уже слишком поздно.

    Мы можем улучшить существующую систему позиционирования с помощью нового виджета. Создадим связку XBL, которой пользователь сможет манипулировать графически, чтобы указать будущее положение заметки. Эта связка будет виджетом специального назначения, в чем-то подобным тегу <colorpicker>, а в чем-то - окошку Print Preview.

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

    15.7.1. Конструирование интерфейсов виджета

    Нам нужно продумать визуальный, XML, JavaScript интерфейсы, а также интерфейс пользовательского ввода, а потом воплотить эти интерфейсы в XBL-коде. Цель виджета - подсказать пользователю информацию о позиционировании и затем сделать эту информацию доступной основному XUL-документу.

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

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

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

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

    Модель XBL-связки, позволяющая пользователю позиционировать заметку.

    Рис. 15.2.  Модель XBL-связки, позволяющая пользователю позиционировать заметку.

    На рисунке 15.2 главный прямоугольник - наш новый виджет. Он имеет три части: задник - <box>, представляющий весь десктоп полностью, опциональный белый <box>, представляющий окно или страницу браузера, и желтый <box>, представляющий заметку, которую нужно позиционировать. Каждый <box> имеет соответствующую надпись, на тот случай, если кому-то что-то не очевидно. Главный прямоугольник получит размеры согласно параметрам разрешения экрана.

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

    Когда заметка позиционирована, вычисляются ее координаты. Если белого прямоугольника (браузера) нет, координаты относятся ко всему десктопу. Браузер может занимать и весь десктоп. Если белый прямоугольник имеет место, координаты отсчитываются от его угла. Белый прямоугольник нужен по психологическим соображениям: люди обычно предпочитают прямоугольники в "золотой пропорции", т.е. в пропорции 1:1,618. Окошки браузера в конце концов обычно оказываются именно в этой пропорции. Белый прямоугольник напоминает нам, как эта идеальная пропорция выглядит.

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

    Третий необходимый интерфейс - XML-интерфейс. Для нас это означает выбор имени тега и множество требуемых атрибутов, которые нам понадобятся, чтобы обеспечить его вид и поведение. Связку можно построить на любом теге, но мы выбираем <noteplacer>. Следующие атрибуты будут иметь специальные значения:

    scale screenx screeny pageless
    

    scale - целое, указывающее размер виджета noteplacer по отношению к размеру десктопа. Например, значение 4 (значение по умолчанию 2) означает, что noteplacer вчетверо меньше целого десктопа.

    screenx означает ширину десктопа в пикселях. Значение по умолчанию дано свойством window.screen.width.

    screeny означает высоту десктопа в пикселях. Значение по умолчанию дано свойством window.screen.height.

    pageless может иметь значение true или false. В случае true белый прямоугольник не появится на фоне виджета. По умолчанию false.

    Теперь вопрос верстки. Будет ли виджет способен изменять размеры? Какова его ориентация? Список вопросов растет. Поскольку виджет noteplacer изображает форму десктопа, он будет иметь фиксированный размер и не будет растягиваться. Он должен всегда иметь горизонтальную ориентацию и не изменять ее.

    Последний интерфейс - JavaScript XBL-интерфейс граничного тега. Мы реализуем следующие свойства:

    top left width height scale screenx screeny pageless 
    setFromEvent(e,primary)
    

    top, left, width, и height соответствуют полям в диалоговом окне Edit.

    scale, screenx, screeny, и pageless относятся к атрибутам XML граничного тега.

    setFromEvent() позиционирует один из углов заметки, используя объект event. Если второй аргумент true, устанавливается верхний левый угол, если нет, правый нижний.

    Так вот, ни с того ни с сего, не приходится думать о поддержке каких-либо существующих интерфейсов платформы (например, nsIAccessible), чего, может, и хотелось бы. Граничный тег будет автоматически поддерживать стандартные интерфейсы DOM для объекта DOM 2 Element, для этого ничего не нужно делать.

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

    15.7.2. Добавляем контент XBL

    Чтобы создать собственно связку, начнем с простого ее скелета. На этом шаге мы определим ее части. Скелет связки приведен в листинге 15.14.

    <?xml version="1.0"?> 
    <bindings id="notetaker" 
        xmlns="http://www.mozilla.org/xbl"
        xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/
        there.is.only.xul" 
        xmlns:xbl="http://www.mozilla.org/xbl"> 
      <binding id="noteplacer"> 
        <resources>    </resources> 
        <content>  </content>
        <implementation> 
          <constructor></constructor> 
          <destructor></destructor>
        </implementation> 
        <handlers></handlers> 
      </binding> 
    </bindings>
    
    Листинг 15.14. Простейший пример XBL связки.

    Набор связок, необходимых для утилиты NoteTaker, мы назвали "notetaker", а связку тега <noteplacer> - "noteplacer". Поскольку здесь нет цепочки наследования, нет и атрибута extends в теге <binding>. Поскольку новый виджет основан на простых XUL-боксах, нет нужды и в атрибуте display. Нам нужно лишь заполнить эти четыре секции связки.

    Начнем с секций <content> и <resources>. Используем контент, служивший основой для рисунка 15.3 и затем рассмотрим, как можно модифицировать его для XBL-системы. Используемый контент приведен в листинге 15.15.

    <stack minwidth="320" minheight="240"> 
      <description value="Screen"/>
        <box class="page" top="0" left="86" minwidth="148"
          minheight="240">
          <description value="Page"/> 
        </box> 
        <box class="note" top="20" left="106" minwidth="40"
          minheight="40"> 
          <description value="Note"/>
        </box> 
    </stack>
    
    Листинг 15.15. Модельный контент XBL-связки noteplacer.

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

    Прежде чем этот код сгодится для секции <content> связки, нужно еще поработать. Существуют некоторые проблемы:

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

    Часть page слишком скучна. Мы можем захотеть поместить здесь что-то более наглядное, например, панель инструментов и меню браузера.

    Фиксированные атрибуты размеров бесполезны для большинства тегов. Реальные значения будут вычисляться на основе атрибутов тега <noteplacer> и текущих значений разрешения экрана.

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

    Для всех названных проблем есть простые решения. Исчезновение блока page - самое простое. Позволим тегу <box>, содержащему блок page, наследовать атрибут pageless из тега <notepicker>:

    <box class="page" xbl:inherits="collapsed=pageless" ...>
    

    Чтобы сделать page не столь скучным, позволим эксплицитному контенту тега <noteplacer> определять контент тега <box class="page">. Аккуратно пересмотрев правила слияния анонимного и эксплицитного контентов, заключаем, что:

    <description value="page"/>
    

    нужно изменить на:

    <children> 
    <description value="page"/> 
    </children>
    

    Если эксплицитный контент есть, он будет добавлен в соответствующее место, если нет, по умолчанию имеется тег <description>.

    Теперь избавимся от фиксированных размеров, указанных в пикселях. Они были получены для виджета на десктопе 640х480, с коэффициентом пропорциональности два. Вместо этого пусть значения вычисляются в конструкторе связки во время выполнения. Мы не можем просто наследовать их из атрибутов тега <noteplacer>, поскольку требуемые значения являются математической комбинацией значений этих атрибутов, а не просто копией. Вскоре мы увидим, как это делается.

    Наконец, размеры шрифтов также пусть вычисляет конструктор. Добавим стиль inline всякому анонимному контенту, и для тега <description>, и для тега <label>. Этот стиль скалирует для нас размеры шрифтов, чтобы они соответствовали размерам виджета. В некоторых случаях текст получится нечитаемым, но и цель виджета - лишь дать некоторое наглядное представление.

    Для выполнения этих двух пунктов добавим атрибут anonid анонимному контенту, который мы хотим позже модифицировать. Немного заглянув вперед, мы увидим, что это тег <stack> и два тега <box>. Результат показан в листинге 15.16. Обратите внимание на префикс xul: namespace каждого XUL-тега.

    <content> 
      <xul:stack anonid="desktop"> 
        <xul:description value="Screen"/> 
          <xul:box xbl:inherits="collapsed=pageless"
            class="page" anonid="page"> 
            <xul:children> 
              <xul:description value="Page"/> 
            </xul:children> 
        </xul:box> 
        <xul:box class="note" anonid="note"> 
          <xul:description value="Note"/> 
        </xul:box> 
      </xul:stack>
    </content>
    
    Листинг 15.16. Часть <content> XBL-связки noteplacer.

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

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

    stack { 
      background-color : background; 
      font-family : -moz-fixed; 
    }
    box.page { 
      background-color : white; 
      border : solid thin; 
      border-color : black; 
    } 
    box.note { 
      background-color : lightyellow; 
      border : solid thin; 
      border-color : yellow; 
    }
    
    Листинг 15.17. Стилевая таблица CSS2 XBL-связки noteplacer.

    В этом случает цвет фона будет тот же, что и у реального десктопа. Шрифт -moz-fixed (системный) имеет одно важное свойство: он может скалироваться до любого размера. 7px это 14px, скалированный с коэффициентом два. Заметьте, что значение пространства имен по умолчанию для подключаемых стилевых таблиц XBL - это пространство имен XUL.

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

    @namespace url("http://www.mozilla.org/keymaster/gatekeeper/
      there.is.only.xul"); 
    xul|stack { ... styles ... };
    

    Стилевая таблица помещается в секцию <resources> спецификации XBL с помощью относительного URL. Она имеет относительный URL, потому что связка noteplacer принадлежит только пакету notetaker и помещается в той же директории, что и он. Это не глобальная связка в глобальном пакете. В общем случае файл CSS следует использовать во всех связках пакета notetaker, и этот файл должен называться notetaker.css. Мы используем другое имя, чтобы подчеркнуть, что этот файл имеет только лишь стилевую информацию для XBL, и избежать конфликта со скинами, созданными ранее. Законченная часть XBL выглядит так:

    <resources> 
      <stylesheet src="noteplacer.css"/> 
    </resources>
    

    Создав контент и стилевую таблицу, мы выполнили половину работ по созданию связки. Мы пока что создали только ее визуальный и XML интерфейсы.

    15.7.3. Добавляем функциональность XBL

    Нам остаются секции <implementation> и <handlers>. Эти две секции определяют интерфейс JavaScript и интерфейс пользовательского ввода. Сначала рассмотрим интерфейс JavaScript, определенный нами в списке требований в предыдущем разделе.

    Очень часто можно увидеть, что XML-атрибуты прямо описываются как свойства JavaScript граничного объекта, и мы можем поступить так же. Это делается просто, как можно увидеть из примера, взятого из связки button-base:

    <property name="type" 
      onget="return this.getAttribute('type');"
      onset="this.setAttribute('type', val); 
      return val;"
    />
    

    Мы не используем это метод, потому что не хотим сохранять состояние нашего виджета в атрибутах XML. Это решение основано на рассмотрении преимуществ и недостатков сохранения состояния в атрибутах. Вот преимущества:

    • Очень часто в случае по умолчанию атрибутов нет, что ускоряет работу виджета.
    • Свойства JavaScript и атрибуты XML автоматически координируются, поскольку первые основаны на вторых.
    • Состояние доступно из стилевых таблиц.
    • Легче доступ к состоянию из C/C++ кода платформы.
    • Граничные теги могут наследовать значения в атрибутах, если они используются в секциях <content> других связок.

    Недостатки сохранения состояния в XML-атрибутах следующие:

    • Доступ к атрибутам осуществляется медленнее, по сравнению с обычными переменными JavaScript.
    • Любая попытка скрыть или упростить информацию бессмысленна, потому что информация уже умышленно раскрыта.
    • Скрипты в связке (и за ее пределами также) вынуждены выполнять специальную проверку на тот случай, если требуемый атрибут отсутствует.

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

    В нашем случае виджет определенно специфичен лишь для единственного приложения, и не является виджетом общего назначения. У нас нет очевидного случая применения его с очевидными значениями по умолчанию. Мы не собираемся добавлять код платформы для его обработки или использовать сложные стилевые приемы. Единственным случаем отсутствия атрибута может быть атрибут pageless, где отсутствие значения можно понять как равенство его false. Для нашего виджета мы предпочтем надежный интерфейс, никогда не дающий нулевых или пустых значений. Остальная часть приложения может использовать этот виджет как настоящий закрытый ящик. Это решение означает, что мы будем применять JavaScript для сохранения состояния виджета.

    В общем и целом, нам нужно теперь создать контент для тегов <field>, <property>, <method>, <constructor> и <destructor> виджета.

    Для тега <field> мы используем золотое сечение. Это не идеально, потому что значение нельзя передать XBL-объекту. Но, по крайней мере, мы проиллюстрируем синтаксис:

    <field name="ratio">(1+Math.sqrt(5))/2</field>
    

    Это математическое выражение - один из способов вычислить золотое сечение.

    Что касается конструктора, заметим, что наш виджет довольно большой, и что он не должен увеличиваться и уменьшаться в размерах в зависимости от изменений окружения. Такой фиксированный размер - нестандартное решение. Но он удовлетворяет нашей цели, т.е. аккуратному отображению десктопа. Это означает, что <constructor> может вычислять размеры виджета лишь раз, и нам не нужно беспокоиться о том, чтобы он был растягиваемым впоследствии. Конструктор также может сохранять информацию о состоянии виджета в JavaScript. Логика и верстки, и состояний показана в листинге 15.18.

    this.getAEBA = function (x,y,z) { 
      return document.getAnonymousElementByAttribute(x,y,z); 
    } 
    this.desktop = getAEBA(this,"anonid","desktop"); 
    this.page = getAEBA(this,"anonid","page"); 
    this.note = getAEBA(this,"anonid","note");
    
    this.d = {}; // protect numbers from string-ification 
    
    this.d.top = 40; 
    this.d.left = 40; 
    this.d.width = 100; 
    this.d.height =  100;
    
    this.d.scale  = att2var("scale", 2); 
    this.d.screenx = att2var("screenx", window.screen.width);
    this.d.screeny = att2var("screeny", window.screen.height); 
    this.d.pageless = att2var("pageless", false);
    
    
    this.d.page_offset = 0; 
    if ( !d.pageless ) 
    d.page_offset = (d.screenx - d.screeny/ratio)/d.scale/2; 
    /* layout the content once */
    this.setAttribute("style","font-size:"+14/d.scale+"px;");
    placeDesktop(); 
    placeNote(); 
    if ( ! d.pageless ) 
    placePage();
    
    Листинг 15.18. Код тега <constructor> XBL-связки noteplacer.

    Свойство getAEBA - всего лишь сокращение для труднопроизносимого метода document.getAnonymousElementByAttribute(). Мы запомним важные DOM-элементы виджета на будущее. Свойство d (для data) сохранит значение состояния data как число, несмотря на тенденцию XBL преобразовывать простые переменные в строки. Все свойства this.d спрятаны от пользователя виджета (специалиста по прикладному программированию, использующего тег <noteplacer>). После того, как эти свойства будут вычислены, часть виджета <content> получит правильные значения атрибутов, включая скалирование текста. Функции att2var(), placeDesktop(), placeNote() и placePage() являются методами объекта, объявленного в начале кода конструктора. Они показаны в листинге 15.19.

    this.att2var = function (name, dvalue)  { 
      var val = this.getAttribute(name); 
      if ( val == "" ) return dvalue; 
      if ( isNaN(val) ) return dvalue; 
        return val - 0; 
      } 
      this.arg2var = function (arg)  { 
      var err = "Bad argument passed to noteplacer binding"; 
      if (arg == "" ) throw err; 
      if ( isNaN(arg) ) throw err; 
      return arg; 
    }
    
    this.placeNote = function () { 
      note.setAttribute("top", d.top /d.scale); 
      note.setAttribute("left", d.page_offset + d.left / d.scale);
      note.setAttribute("minwidth", d.width / d.scale);
      note.setAttribute("minheight",d.height / d.scale); 
    } 
    this.placeDesktop = function () { 
      desktop.setAttribute("minwidth", d.screenx/d.scale);
      desktop.setAttribute("minheight",d.screeny/d.scale); 
    } 
    this.placePage = function () { 
      page.setAttribute("minheight", d.screeny/d.scale);
      page.setAttribute("minwidth", d.screeny/d.scale/ratio);
      page.setAttribute("top", "0"); page.setAttribute("left", 
      d.page_offset); 
    }
    
    Листинг 15.19. Методы тега <constructor> XBL-связки noteplacer.

    Метод att2var() вычисляет значение переменной состояния на основании значения по умолчанию и опционального атрибута XML, который используется, если он есть. Метод arg2var() проверяет, является ли передаваемый аргумент числом. Скоро мы его используем. Остальные методы определяют положение виджета на основании информации о состоянии и некоторых вычислений. Все эти методы, а также метод getAEBA(), принадлежат только виджету. Они не проявляются как свойства DOM-объекта тега <noteplacer>.

    Мы не держим XPCOM-компонентов или других больших объектов во время жизни виджета, так что не от чего и избавляться, когда виджет закрывается. Как следствие, тег <destructor> не будет иметь содержания.

    Благодаря предпринятым нами к настоящему моменту усилиям по сохранению информации о состоянии виджета, реализация тегов <property> очень проста. Листинг 15.20 демонстрирует эти теги.

    <property name="top" 
      onget="return this.d.top;" 
      onset="this.d.top = arg2var(val); 
      placeNote();"
    /> 
    <property name="left" 
      onget="return this.d.left;" 
      onset="this.d.left = arg2var(val); 
      placeNote();"
    />
    <property name="width" 
      onget="return this.d.width;"
      onset="this.d.width = arg2var(val); 
      placeNote();"
    /> 
    <property name="height" 
      onget="return this.d.height;" 
      onset="this.d.height = arg2var(val); placeNote();"
    /> 
    <property name="scale" readonly="true"
      onget="return this.d.scale;"
    />
    <property name="screenx" readonly="true" 
      onget="return this.d.screenx;"
    /> 
    <property name="screeny" readonly="true"
      onget="return this.d.screeny;"
    /> 
    <property name="pageless" readonly="true" 
      onget="return this.d.pageless;"
    />
    
    Листинг 15.20. Теги <property> XBL-связки noteplacer.

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

    Вернемся к единственному требуемому тегу <method>.

    Координаты виджета, используемые и в атрибутах XML, и в свойствах JavaScript вычисляются относительно панели, на которую помещена заметка. Однако, когда наступает событие DOM, порожденный объект DOM Event имеет координаты, вычисляемые по отношению ко всему окну. Это другая координатная система. Удобный метод setFromEvent() позволяет нам позиционировать угол заметки, используя этот объект. Виджет выполняет требуемое преобразование координат. Метод показан в листинге 15.21.

    this.getCoords = function(x,y) { 
      return { 
        x: (x - this.boxObject.x - d.page_offset) * d.scale, 
        y: (y - this.boxObject.y) * d.scale 
      }; 
    }
    <method name="setFromEvent"> 
      <parameter name="evt"/> 
      <parameter name="cornerFlag"/> 
      <body>
        <![CDATA[ 
          var coords = getCoords(evt.clientX, evt.clientY); 
          if (cornerFlag) { 
            d.width += d.left - coords.x;
            d.height += d.top - coords.y; 
            d.top = coords.y; 
            d.left = coords.x; 
          }
          else { 
            d.width = coords.x - d.left; 
            d.height = coords.y - d.top; 
          }
          placeNote(); 
        ]]> 
      </body> 
    </method>
    
    Листинг 15.21. Тег <method> XBL-связки noteplacer.

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

    Последняя часть спецификации связки - секция <handlers>. У нас есть два обработчика, оба для кликов - событий DOM 2, но для разных кнопок мыши. Для первичной кнопки мы вводим новую частную переменную, на этот раз называемую this.d.topclick. Если она имеет значение true, клик первичной кнопкой установит значения верхнего левого угла. Если false, нижнего правого. Инициализируем переменную значением true в конструкторе. Для вторичной кнопки используем теорему Пифагора ("квадрат гипотенузы прямоугольного треугольника равен сумме квадратов катетов"), чтобы вычислить угол, находящийся по диагонали, ближайший к месту клика. Затем значения координат этого угла обновляются. Код, получившийся в результате, показан в листинге 15.22.

    <handlers> 
      <handler event="click" button="0">
        <![CDATA[
          this.setFromEvent(event,d.topclick); 
          this.d.topclick = !this.d.topclick; 
        ]]> 
      </handler> 
      <handler event="click" button="2">
        <![CDATA[ 
          var coords = this.getCoords(event.clientX,event.clientY); 
          var dist1, dist2; 
          with (Math) { 
            dist1 = sqrt(pow(coords.x-d.left,2) 
              + pow(coords.y-d.top,2)); 
            dist2 = sqrt( pow(coords.x - (d.left + d.width), 2) 
              + pow(coords.y - (d.top + d.height), 2) ); 
          } 
          this.setFromEvent(event, (dist1 < dist2)); 
        ]]>
      </handler> 
    </handlers>
    
    Листинг 15.22. Теги <handler> XBL-связки noteplacer.

    Используя секцию CDATA, мы экономим время, которое в противном случае потратили бы на отладку. Функции обработчиков снова используют публичный метод setFromEvent() и частный метод getCoords(), так что их логика очень проста.

    С окончанием работы над секцией <handlers> заканчивается работа над связкой в целом, а это почти 150 строк кода. Поскольку изменений больше не требуется, все что нам остается - это попробовать ее в работе.

    15.7.4. Встраиваем готовую XBL-связку

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

    Чтобы связать связку с тегом, используем стилевое правило:

    noteplacer { -moz-binding : url("noteplacer.xml#noteplacer"); }
    

    Пока поместим эту строку в файл xulextras.css. Этот файл будет расположен в той же директории, что и весь контент noteplacer. Эта стилевая таблица добавит к стандартному множеству тегов XUL новый тег <noteplacer>.

    При тестировании нового виджета мы хотим увидеть следующие вещи:

    Виджет правильно вписывается в окружающий контент.

    Виджет правильно реагирует на пользовательский ввод, включая изменения размеров окна.

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

    Эксплицитный контент, передаваемый виджету, дает желаемый эффект.

    С этой целью создадим тестовый XUL-документ, приведенный в листинге 15.23.

    <?xml version="1.0"?> 
    <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> 
    <?xml-stylesheet href="xulextras.css" type="text/css"?> 
    <!DOCTYPE window> 
      <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/ 
        there.is.only.xul" onload="install()"> 
        <script> 
          var np; function install() { 
            np = document.getElementById("test"); 
            np.addEventListener("click", report, false); 
          } 
          function report() { 
            var str = ""; 
            str += "Top: "   + np.top + "\n"; 
            str += "Left: "   + np.left + "\n"; 
            str += "Width: "   + np.width + "\n"; 
            str += "Height: "   + np.height + "\n";
            str += "ScreenX: " + np.screenx + "\n"; 
            str += "ScreenY: " + np.screeny + "\n"; 
            str += "scale: "  + np.scale + "\n";
            alert(str); 
          } 
        </script> 
        <description>Before Test </description> 
          <hbox>
            <description value="Left of Test"/> 
              <noteplacer screenx="1024" screeny="768" scale="4"> 
                <toolbox flex="1"> 
                  <toolbar grippyhidden="true"> 
                    <description value="File"/> 
                    <description value="Edit"/> 
                    <description value="View"/> 
                    <description value="Go"/>
                    <spacer flex="1"/> 
                  </toolbar> 
                  <spacer style="background-color:white" flex="1"/> 
                </toolbox> 
              </noteplacer> 
            <description value="Right of Test"/> 
          </hbox> 
            <description>After Test </description> 
        </window>
      </handler> 
    </handlers>
    
    Листинг 15.23. Тестовая страница для XBL-связки noteplacer.

    Этот документ упаковывает тег <noteplacer> в соответствующий контент и добавляет имитацию панели инструментов. Он также содержит обработчик событий этого тега. Обработчик событий использует обработчики кликов, определенные в связке. Документ показан на рисунке 15.3.

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

    Тестовый XUL-документ с тегом <noteplacer>.

    Рис. 15.3.  Тестовый XUL-документ с тегом <noteplacer>.

    Если обработчик кликов получает сигнал о событии, появляется панель предупреждения с сообщением о текущей позиции заметки. Панель предупреждения показана на рисунке 15.4.

    Диагностическое предупреждение с сообщением о состоянии виджета <noteplacer>

    Рис. 15.4.  Диагностическое предупреждение с сообщением о состоянии виджета <noteplacer>

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

    Мы видим, как тег <noteplacer> может быть интегрирован в приложение NoteTaker с помощью JavaScript. Из диалогового окна Edit он может быть загружен в отдельное второе диалоговое окно или встроен где-либо в уже существующий <tabbox>. Когда диалоговое окно закрывается, обработчик событий onclose считывает состояние виджета и копирует значения в соответствующие поля панели Edit.

    Сам по себе наш виджет даст пользователю немного подсказок по его использованию. Как и многие другие теги XUL, <noteplacer> функционален, но не очевиден. Этот тег должен быть окружен другим контентом перед предъявлением его пользователю. На рисунке 15.5 приведен один из возможных сценариев.

    Использование тега <noteplacer> в приложении.

    Рис. 15.5.  Использование тега <noteplacer> в приложении.

    На этом раздел "Практика" данной главе заканчивается.

    15.8. Отладка: диагностика XBL

    В связках XBL нет ничего загадочного; и объект window, и объект document доступны всем свойствам, методам, обработчикам событий, и конструкторам/деструкторам кода связки. Все техники отладки, доступные обычным скриптам, также доступны и скриптам в XBL.

    Нет ничего загадочного и в работе связок с RDF. Синтаксические ошибки в документе XBL выводятся на консоль JavaScript.

    Две наиболее часто возникающие проблемы при создании XBL-связки связаны с пространствами имен XML и внутренними свойствами JavaScript. Запомните о пространствах имен следующее:

    XBL-свойсво inherit должно иметь префикс с идентификатором пространства имен наподобие xbl:, потому что пространство имен, используемое по умолчанию для тега, который содержит это свойство, всегда либо XUL, либо HTML.

    XBL должен иметь отдельное пространство имен, потому что некоторые имена его тегов совпадают с именами тегов других приложений XML, такими как XUL, RDF и HTML.

    Если применяется стандартный набор пространств имен, где по умолчанию используется XBL, каждый XUL или HTML тег в секции <content> должен будет иметь префикс соответствующего пространства имен. В противном случае никакой контент не будет выведен на экран.

    В свойствах JavaScript XBL-связок допускаются две простые оплошности:

    Забывают указывать префикс this для переменных связки.

    Забывают, что когда простые переменные с префиксом this сохраняются, они преобразуются в строку.

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

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

    Другое простое решение - добавить к каждой создаваемой связке такой тег:

    <field name="loaded" readonly="true">true</field>
    

    Перед обращением к связке из скрипта проверьте данное свойство; если оно имеет значение true, значит, связка доступна.

    Более систематическое решение состоит в использовании части связки <constructor>. Вот такой кусочек кода - простая регистрационная система:

    with (window.document) bindTotal = (bindTotal) ? ++bindTotal : 1;
    

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

    Последнее, и глобальное, решение проблемы асинхронности - использовать метод loadBindingDocument().

    Наконец, XBL-виджеты имеют некоторые ограничения, связанные с секретностью. Если два документа, загружаемые с двух разных URL, должны иметь полный доступ друг к другу, они должны пройти тест Mozilla, который называется "Same Origin". Этот тест требует, чтобы оба URL принадлежали одному и тому же серверу. XBL-связки также должны проходить этот тест, если они установлены там, где требования секретности могут быть нарушены. Приложение XUL с одного сервера не может стандартным способом использовать XBL-связку с другого. Чем возиться со сложной процедурой сертификации и цифровой подписи кода, обычно проще инсталлировать XBL-связку в chrome.

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

    15.9. Итоги

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

    XBL-часть платформы Mozilla обогащает платформу интерактивными, визуальными виджетами, придавая им очень гибкие и объектно- ориентированные свойства. XBL-связками являются почти каждый тег XUL и многие теги HTML. Действия по умолчанию, определенные этими связками, и делают эти теги столь простыми и гибкими в работе.

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


    об авторе

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





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