Лекция 10:

Окна и панели

10.9. Практика: диалоговые окна NoteTaker

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

  1. Заменить корневой тег <window> на <dialog> для диалогового окна Edit.
  2. Заменить обработчики событий, связанные с тегами <button>, на обработчики, связанные с <dialog>.
  3. Реализовать в главном окне команду notetaker-open-dialog для создания диалогового окна Edit при помощи метода window.openDialog().
  4. Реализовать команду notetaker-close-dialog.
  5. Выработать и реализовать стратегию обращения с данными, которые используются более чем одним окном.
  6. Решить, окном какого типа должна быть заметка NoteTaker.

Прежде всего, обратимся к окну Edit. Заменить тег <window> на <dialog> несложно. Новый корневой тег точно так же позволяет указать текст для заголовка окна:

<dialog xmlns="http://www.mozilla.org/keymaster/
 gatekeeper/there.is.only.xul"
	id="notetaker.dialog"
  title="Edit NoteTaker Note"
  onload="execute('notetaker-load')">

Нам больше не нужно вручную создавать кнопки для окна, поскольку для <dialog> они создаются автоматически. Однако в то же время мы кое-что теряем – мы не можем просто использовать тег <command> для <dialog>, поскольку с каждым тегом можно связать лишь одну команду. При желании можно было бы отключить автоматическое создание кнопок и сохранить старые теги <button> с <command> для каждого из них. Однако логично использовать собственные кнопки диалогового окна, имеющие стандартный вид на каждой из платформ. Вместо команд мы можем добавить следующие обработчики событий:

ondialogaccept="execute('notetaker-save');
 execute('notetaker-close-dialog');"
ondialogcancel="execute('notetaker-close-dialog');"

Поскольку диалоговое окно автоматически закрывается при нажатии кнопки Cancel или кнопки "Закрыть" на заголовке окна, в этих случаях мы можем обойтись без вызова команды notetaker-close-dialog. В дальнейшем при необходимости мы сможем добавить обработчик onclose, а пока нам необходим только ondialogaccept.

Таким образом, нам осталось реализовать команды -open- и -close-. Пока у нас нет полностью интегрированной панели инструментов, но мы можем использовать фрагмент XUL для тестирования команд. Функция action() панели инструментов нуждается в очень простом улучшении:

if ( task == "notetaker-open-dialog" )
{
  window.openDialog("editDialog.xul","_blank","modal=yes");
}

В данном случае мы используем параметр modal – главное окно браузера будет недоступно, пока пользователь работает с диалоговым окном. Это позволяет нам не беспокоиться о переходах фокуса ввода между двумя окнами или, что еще хуже, между полями ввода различных окон.

Столь же простое усовершенствование требуется и для функции action() диалогового окна Edit:

if (task == "notetaker-close-dialog")
  window.close();

Наконец, мы должны позаботиться об управлении данными. NoteTaker позволяет создавать не более одной заметки для каждого URL и в каждый момент работать только над одной заметкой. Однако само наше приложение уже содержит два окна. Как панель инструментов, так и диалоговое окно содержат поля, в которые пользователь может вводить данные. Какое из окон должно хранить состояние текущей заметки?

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

var note = {
  url : null;
  summary : "",
  details : "",
  chop_query : true, home_page : false,
  width : 100, height : 90, top : 80, left : 70
}

Для доступа к этому объекту из диалогового окна Edit используется следующий простой синтаксис:

window.opener.note

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

При использовании свойства opener следует быть осторожным. Хотя с его помощью мы получаем доступ к другому окну, код JavaScript продолжает выполняться в контексте текущего окна, которому принадлежит исходный скрипт. Такие вспомогательные функции, как setTimeout(), setAttribute() и другие всегда применяются к текущему окну даже в том случае, когда их вызывает функция, определенная в другом окне. Листинг 10.4 демонстрирует логику функции action(), определенной в диалоговом окне.

if (task == "notetaker-save")
{
  var field, widget, note = window.opener.note;
  for (field in note)
  {
    widget = document.getElementById("dialog." + field.replace(/_/,"-");
    if (!widget) continue;
    if (widget.tagName == "checkbox")
      note[field] = widget.checked;
    else
      note[field] = widget.value;
  }
}
if (task == "notetaker-load" )
{
  var field, widget, note = window.opener.note;
  for (field in note)
  {
    widget = document.getElementById("dialog." + field.replace(/_/,"-");
    if (!widget) continue;
    if (widget.tagName == "checkbox")
      widget.checked = note[field];
    else
      widget.value = note[field];
  }
}
Листинг 10.4. Сохранение данных диалогового окна в главном окне и загрузка данных в диалоговое окно.

Первая и вторая половины этого листинга выполняют противоположные действия. Оператор continue позволяет игнорировать те свойства объекта note, которые неизвестны диалоговому окну. Более тщательно подбирая имена свойств, мы могли бы избежать необходимости замены символов (с учетом того, что символ "-" является недопустимым в именах свойств JavaScript), но в данном случае это несущественно. Теперь объект note основного окна является "официальным" представлением заметки, а диалоговое окно способно "загружать" данные из этого объекта и "сохранять" их в нем. В дальнейшем нам понадобится расширить логику приложения для того, чтобы сохранять эти данные во "внешнем мире".

Наконец, займемся представлением заметки с точки зрения пользователя. Цель приложения NoteTaker – позволить снабжать web-страницы комментариями, поэтому заметка должна отображаться поверх просматриваемой страницы. Данные заметки хранятся на локальном компьютере, и ее отображением управляет локальный код, однако страница может быть загружена с любого сайта. Поскольку наше приложение установлено в chrome, оно считается защищенным и имеет право изменять отображение страницы любым образом – например, отобразить заметку поверх части страницы.

Такая заметка могла бы быть реализована с помощью XUL, XBL и JavaScript. Как известно, web-страница в браузере отображается внутри тега <iframe>, который является содержимым тега <tabbox>, а тот, в свою очередь, является содержимым тега <tabbrowser>. Если бы <iframe> был "завернут" в тег <stack>, мы могли бы расположить нашу заметку в этом стеке поверх web-страницы. Мы могли бы задать размер и положение заметки при помощи относительного позиционирования CSS, и web-страница была бы видна всюду за исключением того места, где она перекрыта заметкой. Заметка могла бы быть любым простым тегом XUL, например тегом <box> с рамкой, фоном и содержимым. Представьте себе записку, приклеенную к окну, - вы можете видеть сад за окном, хотя часть стекла закрыта запиской.

Однако эта стратегия потребовала бы от нас изменить XBL-определение тега <tabbox>. Технически это возможно, однако переопределение одного из стандартных тегов XUL – серьезный шаг, который требует значительного тестирования. Фактически, нам пришлось бы проверить корректность работы нового тега со всеми приложениями, выполняемыми на платформе Mozilla, включая сам браузер, - слишком много труда для нашего несложного приложения.

Поэтому мы используем другую стратегию – создание заметки средствами HTML и CSS. Из приложения, установленного в chrome, мы можем получить доступ к объектной модели отображаемой web-страницы. Средствами DHTML мы можем добавить к странице тег <span> с необходимым содержимым. При помощи стилей может быть задано абсолютное позиционирование тега, а также большое значение свойства z-index, которое обеспечит отображение заметки поверх страницы. Существует незначительная вероятность –около одной миллиардной, - что заметка окажется перекрыта частью страницы, однако для наших целей этой величиной можно пренебречь. Данная стратегия хороша тем, что она не затрагивает остального chrome, и мы будем использовать именно ее.