Опубликован: 10.12.2007 | Уровень: специалист | Доступ: платный
Лекция 6:

События

6.4. Альтернатива: таблицы стилей

Специальных стилей, влияющих на обработку событий, в Mozilla нет. Некоторые из псевдостилей CSS 2 вроде :active используются для текущего элемента в фокусе или текущего выделенного элемента.

6.5. Практика: ввод данных в NoteTaker

В этот раз мы добавим в диалог NoteTaker поддержку работы с клавиатуры и несколько обработчиков событий. Дальнейшее обсуждение обработчиков событий приводится в "Списки и Деревья" , "Списки и деревья". Здесь мы будем следовать систематическому подходу. В текущем разделе будет представлен простой вводный код.

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

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

Для этого мы изменим все кнопки в диалоге так, как показано в листинге 6.14.

// до 
<toolbarbutton label="Edit" onclick="action('edit');"/> 
<toolbarbutton label="Keywords" onclick="action('keywords');"/> 
<button label="Cancel"/> <button label="Save"/>
// после 
<toolbarbutton label="Edit" accesskey="E" onclick="action('edit');"/> 
<toolbarbutton label="Keywords" accesskey="K" 
  onclick="action('keywords');"/> 
<button label="Cancel" accesskey="C"/> 
<button label="Save" accesskey="S"/>
Листинг 6.14. Добавление accesskey в NoteTaker

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

<description>Summary</description>

на эту:

<label value="Summary" accesskey="u"/>

В этом случае мы не можем для Summary использовать S, так как эта буква уже используется для Save. Когда мы сделаем такие изменения для всех заголовков, наше окно будет выглядеть так, как на рисунке 6.3.

Диалог NoteTaker с подсвеченными клавишами доступа

Рис. 6.3. Диалог NoteTaker с подсвеченными клавишами доступа

Но после внесения изменений нажатие на эти клавиши ни к чему не приводит. Теперь нам нужно добавить несколько тегов <key>, как показано в листинге 6.15.

<keyset> 
  <key key="e" oncommand="action('edit')"/> 
  <key key="k" oncommand="action('keywords')"/> 
  <key key="c" oncommand="action('cancel')"/> 
  <key key="s" oncommand="action('save')"/> 
  <key key="u" oncommand="action('summary')"/> 
  <key key="d" oncommand="action('details')"/> 
  <key key="o" oncommand="action('options')"/> 
  <key key="z" oncommand="action('size')"/> 
</keyset>
Листинг 6.15. Связывание клавиш доступа с действиями в NoteTaker

Атрибут key не зависит от регистра; если нам требуется именно заглавная S, нужно добавить атрибут modifiers="shift". Если мы хотим использовать F1, следует задействовать атрибут keycode и виртуальный символ вместо key и печатаемого символа. Мы можем использовать код, созданный ранее, так как функция action() принимает как аргумент единственную инструкцию. Если изменить action() так, чтобы она включала такую строку:

alert("Action: " + task");

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

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

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

.disabled { 
  border : solid; padding : 2px; margin : 2px; 
  border-color : darkgrey; color : darkgrey 
}

На панели правки есть четыре области ( Summary, Details, Options и Size ) и в сумме восемь отображаемых блоков, каждый из которых, возможно, требуется обновить. Мы можем сгруппировать эти восемь блоков в четыре с помощью тегов <broadcaster> и атрибутов observes. Мы добавляем источники событий в листинге 6.16, который для начала ничего не указывает.

<broadcasterset> 
  <broadcaster id="toggle-summary"/> 
  <broadcaster id="toggle-details"/> 
  <broadcaster id="toggle-options"/> 
  <broadcaster id="toggle-size"/> 
</broadcasterset>
Листинг 6.16. Источники событий для отключения частей диалога NoteTaker

Мы добавим всем восьми блокам атрибут observes=, связывая каждый из них с одним из четырех источников событий, так что

<box id="dialog.top" class="temporary">

становится

<box id="dialog.top" class="temporary" observes="toggle-size"/>

Если у любого из тегов <broadcaster> появится атрибут class="disabled", этот атрибут будет передан блокам, наблюдающим за ним, и их стиль изменится. Мы можем проверить эту систему, временно задав class="disabled" любому тегу <broadcaster>. Если сделать это для последнего из них ( "toggle-size" ), диалог будет выглядеть так, как на рисунке 6.4.

Диалог NoteTaker с частью "отключенных" параметров

Рис. 6.4. Диалог NoteTaker с частью "отключенных" параметров

Так как этот код работает, мы теперь можем связать его с пользовательским вводом. Сначала - клавиши. Для этого мы подправим метод action(). В листинге 6.17 показан новый код.

function action(task) {
  if ( task == "edit" || task == "keywords" ) { 
    var card = document.getElementById("dialog." + task); 	
    var deck = card.parentNode;
    if ( task == "edit" ) deck.selectedIndex = 0; 
    if ( task == "keywords") deck.selectedIndex = 1; 
  } 

  if ( task == "summary" || task == "details" || 
   task == "options" || task == "size" ) { 
    var bc = document.getElementById("toggle-" + task);
    var style = bc.getAttribute("class"); 
      if ( style == "" || style == "temporary" ) 
       bc.setAttribute("class","disabled"); 
    else 
    bc.setAttribute("class","temporary"); 
  } 
}
Листинг 6.17. Код для изменения стиля частей диалога NoteTaker

Этот код меняет только теги источников событий, а платформа берет на себя все остальное. Если необходимо, мы можем проверить этот код, напрямую вызывая метод action(), возможно, из обработчика onload тега <window>. Это так же легко проверить, нажимая соответствующую клавишу.

Поддержка мыши несколько сложнее. Где же именно в окне пользователь должен щелкнуть мышью, чтобы проявился какой-то эффект? Мы могли бы создать обработчик событий для каждого тега, но это не очень разумно. Вместо этого мы воспользуемся моделью размещения XUL-элементов и моделью DOM-событий. Мы убедимся, что каждая из четырех частей окна хранится в отдельном XUL-теге - это то, что касается размещения. А вверху дерева документа создадим обработчик, который будет перехватывать события onclick и определять, к какой из частей это событие относится - это DOM-события.

Прежде всего, обратимся к XUL-части задачи. Разделы Summary и Details не находятся в одном блоке, так что мы поместим каждую пару <label> и <box> в дополнительный <vbox>. С точки зрения отображения это необязательно, но для нашего скрипта - удобно. Теперь у нас есть две части окна, содержащиеся в тегах <vbox>, и две - в тегах <groupbox>. Каждому из этих четырех тегов мы добавим атрибут subpanel с именем соответствующего раздела:

<groupbox subpanel="size"/>

В XUL у этого атрибута нет какого-либо особого значения; мы только что придумали его сами. Это удобно размещенные данные.

Далее мы напишем функцию обработки событий, которая будет перехватывать щелчки мышью. Мы хотим перехватывать их с самого начала, то есть в фазе перехвата. XUL-атрибут onclick годится только для фазы возврата, так что используем addEventListener() из JavaScript. Функция обработчика и установка обработчика показаны в листинге 6.18.

function handle_click(e) {
  var task = ""; 
  var tag = e.target; 
  while ( (task = tag.getAttribute("subpanel")) 
    == "" && tag.tagName != "window" ) 
  tag = tag.parentNode; 
  if ( task != "") 
  action(task); 
}
document.addEventListener("click", handle_click, true);
Листинг 6.18. Код для перехвата событий щелчка мышью и определения соответствующего действия

Так как этот обработчик устанавливается для объекта Document (тег <window> ) и будет запускаться в фазе перехвата, он будет получать события onclick раньше всех. Он смотрит на переданный объект события DOM 2 и определяет цель этого события. Эта цель - самый глубоко вложенный тег из всех оказавшихся под указателем мыши. Затем код идет вверх по DOM-дереву от этой цели в поисках тега с атрибутом subpanel. Если он находит такой тег, запускается действие со значением этого атрибута. Если такой тег не найден, ничего не происходит. Затем обработка события в дереве продолжается обычным образом.

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

На этом мы пока завершим практику. Мы не сохраним все внесенные здесь изменения, но в целом это все равно были полезные эксперименты.

Дмитрий Гуменюк
Дмитрий Гуменюк
Россия, Звенигород
Konstantin Grishko
Konstantin Grishko
Россия, Москва, Московский финансово-промышленный университет "Синергия", Москва