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

XBL-связки

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 строк кода. Поскольку изменений больше не требуется, все что нам остается - это попробовать ее в работе.