Опубликован: 10.12.2007 | Доступ: свободный | Студентов: 822 / 20 | Оценка: 5.00 / 5.00 | Длительность: 58:33:00
Лекция 15:

XBL-связки

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, может вызывать иные процессы, получая или устанавливая ее значение.

Вспомним "Скрипты" , "Сценарии". Свойство 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), два конструктора не могут выполняться в одно и то же время.