14. Обработка событий

События в модели DOM уровня 0

Первый способ

Второй способ

В этой модели обработчики событий объявляются одним из двух способов:

  • присвоением ссылки на обработчик событийному свойству объекта;
  • заданием обработчика в виде значения событийного атрибута в открывающем теге соответствующего элемента.

Предположим, нужно обработать щелчок на первой картинке гипертекстового документа — показать на экране модальное окно alert с надписью.

Событийное свойство объекта

Следуя первому способу, можно написать:


document.images[0].onclick = function () {alert("На мне щелкнули!");};

В этой записи для доступа к объекту, построенному для первой картинки документа, взята коллекция images (определенная в DOM уровня 0 и продолжающая работать в современных браузерах). Первый член этой коллекции с индексом 0 и есть объект, построенный для первой картинки, а его свойство onclick  — нужное событийное свойство этого объекта.

В правой части оператора присваивания расположен функциональный литерал, показывающий модальное окно alert. Сам литерал, располагается, конечно, где-то в памяти, а свойству onclick присваивается ссылка на него.

Можно было задать обработчик, используя функцию с именем:


document.images[0].onclick = work; // Присвоили ссылку на функцию
function work()                    // А это сама функция
{
  alert("На мне щелкнули!");
}

Если в теге IMG первой картинки документа присутствует атрибут name="pic0",


<IMG name="pic0">

то доступ к соответствующему объекту возможен и с использованием одного из следующих синтаксисов:


document.images["pic0"] // Синтаксис ассоциативного массива
document.images.pic0    // Точечный синтаксис
document.pic0           // И даже так, предполагая, что имя "pic0"
                        // уникально в пространстве имен атрибута name.
                        // Получается, что свойство pic0 является в этом 
                        // случае свойством объекта document

Событийный атрибут элемента

Для привязки обработчика к первой картинке вторым способом в HTML-коде можно написать:


<IMG onclick='alert("На мне щелкнули!")'>

По этой записи можно подумать, что обработчиком события является функция alert, но это не так. Значение событийного атрибута рассматривается как JavaScript-код (он может быть любой длины, содержать циклы, ветвления, вызовы функций). Этот код автоматически оборачивается в анонимную функцию, и ссылка на неё записывается в свойство onclick соответствующего объекта DOM (как и в первом) варианте.

Ключевое слово this

Независимо от способа привязки, ключевое слово this в обработчике будет относиться к объекту, с которым связан обработчик. Это понятно, ведь обработчик является свойством этого объекта, значит, контекст исполнения обработчика будет связан именно с ним.

Для смены изображения по щелчку, можно написать такой код:


document.images[0].onclick = function () {this.scr="pic/pic1.gif";};

или такой:


<IMG onclick='this.scr="pic/pic1.gif"'>

Но из этого не следует, как это вначале не покажется странным, что ключевое слово this всегда будет связано с объектом, на котором событие возникло.

Всплытие события

Событие, возникнув на элементе, начинает «всплывать» вверх по HTML-иерархии, передаваясь сначала родителю, а от него его родителю и так далее, пока не достигнет корня (которым в DOM является объект document).

На самом деле в «ранней» модели DOM события не всплывали. Но этот факт имеет только исторический интерес. Современные браузеры всплытие поддерживают. Всплытие выполняется всегда, независимо от того, какими средствами задаются обработчики, средствами DOM уровня 0 или средствами DOM W3C.

Для того чтобы разобраться со всплытием событий, соорудим испытательный стенд. Элемент IMG в этом коде погружен в P, абзац P — в BODY, а BODY, соответственно, в HTML. За каждым из элементов этой семейной цепочки закрепим в файле main.js один и тот же обработчик say(), реагирующий на щелчок мыши.

Файл index.htm


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
  "http://www.w3.org/TR/html4/loose.dtd">
<HTML id="html" lang="ru">
  <HEAD>
    <META http-equiv="Content-Type" 
          content="text/html; charset=windows-1251">
    <META http-equiv="imagetoolbar" content="no">
    <SCRIPT type="text/javascript" src="main.js"></SCRIPT>
    <TITLE>Тестируем всплытие</TITLE>
  </HEAD>
  <BODY id="body">
    <H1>Тестируем всплытие</H1>

    <P id="p">
<IMG id="pic" src="pic/pic.png" width=119 height=91 alt="" title="">
    </P>

    <H2>Протокол работы обработчиков событий</H2>

  </BODY>
</HTML>

Файл main.js (фрагмент)


// Назначим единственный обработчик события всем объектам
  document.getElementById("pic").onclick  = say;
  document.getElementById("p").onclick    = say;
  document.getElementById("body").onclick = say;
  document.getElementById("html").onclick = say;
 
 // Сам обработчик
 function say()
 {
   var text = "id элемента: " + this.id;
   appendP(text); // Добавим абзац в документ
 }

Полные коды этого испытательного стенда приводятся на странице примеры («Пример 01. Тестируем всплытие»).

Запустите index.htm и щёлкните на картинке. На странице появится протокол работы обработчиков:

id элемента: pic
id элемента: p
id элемента: body
id элемента: html

Всплытие события

Видим, сначала сработал обработчик, закреплённый за картинкой, он вывел сообщение «id элемента: pic». Но на этом событие «не успокоилось», оно стало всплывать вверх по иерархическому дереву. Последовательно сработали обработчики, сначала в родителе P, потом в дедушке BODY и, наконец, в основателе рода — HTML.

А теперь посмотрите, как формировалось сообщение в функции say:


var text = "id элемента: " + this.id;

Функция say относила ключевое слово this к объекту, в контексте которого вызывалась, и этот контекст совпал с источником события только один раз (для самой картинки).

Вот что означало, странное на первый взгляд утверждение предыдущего раздела: «ключевое слово this в обработчике не всегда связано с объектом, на котором событие возникло».

Ключевое слово this всегда связано с объектом, который вызвал обработчик. При всплытии, объект вызова мог не совпасть с объектом-источником события.

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

Объект Event

Независимо от того, как создан обработчик, при помощи событийного свойства или событийного атрибута, при вызове в него автоматически передаётся (в качестве единственного аргумента) объект, обладающий интерфейсом Event.

Свойства этого объекта дают детальную информацию о возникшем событии, а методы позволяют управлять дальнейшей «судьбой» события. В частности, метод stopPropagation() позволяет остановить распространение события, а метод preventDefault() — отменить действие, предусмотренное для события по умолчанию (например, загрузку нового документа по щелчку на гипертекстовой ссылке). Эти методы были включены в DOM W3C, но работают и в обработчиках, заданных по старинке.

Таким образом, обработчик можно задавать с аргументом так:


document.images[0].onclick = function (event) 
{ 
  event.stopPropagation(); // Прекращение распространение события
  event.preventDefault();  // Отмена действия по умолчанию
  ...
};

или так:


document.images[0].onclick = work; // Присвоили ссылку на функцию
function work(event)               // А это сама функция
{
  event.stopPropagation(); // Прекращение распространение события
  event.preventDefault();  // Отмена действия по умолчанию
  ...
}

Однако существует проблемы, связанные с браузером IE.

Во-первых, браузер IE не передает объект Event в обработчик. Вместо этого он записывает ссылку на этот объект в свойство event глобального объекта window. Проблема решается просто. Так:


function work(event)
{
  if (!event) event = window.event;

  ...
}

или так:


function work(event)
{
  event = event || window.event;

  ...
}

Во-вторых, в объекте Event от IE нет методов stopPropagation и preventDefault. Вместо этого есть свойства:

Кроме того, действие по умолчанию отменяется, если сам обработчик возвращает false — это поведение унаследовано от ранней модели DOM и продолжает работать во всех браузерах.

Универсальный обработчик может выглядеть так:


function work(event)
{
  event = event || window.event;

  // Прекращение распространение события
  if(event.stopPropagation)       
       event.stopPropagation();    // Модель W3C
  else event.cancelBubble = true;  // Модель IE

  // Отмена действия по умолчанию
  if(event.preventDefault)
       event.preventDefault();     // Модель W3C
  else event.returnValue = false;  // Модель IE

  ...

  return false; // Отмена действия по умолчанию (во всех браузерах)
}