Начало · Справочники · Курсы · Разговоры

leechy.ru · Сайт почти придуман

Перехват событий скриптом

Кроме как атрибутами HTML-элементов, обработчик событий можно присвоить и с помощью скрипта. На самом деле есть два способа перехвата событий — используя свойств элементов, являющимися синонимами атрибутов и с помощью специальных методов, добавляющих обработчиков. В чем разница?

События как свойства элементов

Это, можно сказать, наследие атрибутов. Суть в том, что у каждого элемента (в зависимости от объектной модели браузера) есть свойства, которые соответствуют атрибутам о которых писал в предыдущей статье о перехвате. Эти свойства называются также, как и атрибуты, но в отличие от нечуствительному к регистру букв HTML'e они должны быть написаны только строчными буквами. Пример кода кнопки, которая при клике выдает содержание свойства onclick:

Пример 1: вывод функции-обработчик события
<input type="button" value="onClick" onClick="alert(this.onclick);">

Если попробовали, то наверняка заметили, что на самом деле onclick — это не просто строка, которая содержит alert(this.onclick);, а функция. Ее имя в данном случае не имеет значение, в NN4+/Mozilla и MSIE она называется также как и свойство onclick), в Opera и MSIE для Windows она anonimous, в других браузерах она может быть вообще без имени... это неважно, потому, что все равно нельзя вызвать ее по этому имени.

То, что на самом деле важно, это то, что в NN4+/Mozilla у этой функции есть параметр (event), а в большинствe остальных браузерах его нет. Что «правильнее» нельзя сказать точно, в DOM2 Events описан параметр для метода handleEvent, но кроме NN6/Mozilla практически никто не поддерживает DOM2, поэтому будем жить с тем, что есть.

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

Но вернемся чуть назад. Видели, что обработчик присвоенный атрибутом — это функция, а это означает, что если присваивать обработчик с помощью свойства, то тоже нужно присваивать именно функцию, а не просто строку. Функции в JavaScript присваиваются также, как и все другие объекты — знаком равенства. Только нужно запомнить, что после имени функции не ставятся скобки:

function clickElem(evt) {
   alert('Кликнули!');
}

elementPtr.onclick = clickElem;

В этом примере elementPtr — это указатель (пойнтер) на элемент, событие которого перехватываем. Если это элемент формы или ссылка, то можете воспользоваться стандартными коллекциями, чтобы достучаться до него (anchors, links, forms, elements), если это другой элемент, то каким образом можно получить указатель на него я уже рассказывал, например в статье «Играем в прятки».

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

elementPtr.onclick = function() { alert('Кликнули!'); }

Такой прием обычно используеться только в случае, если обработчику нужно передать какой-нибудь параметр, значение которого нет среди свойств объекта event... о котором наверное следует поговорить.

Объект Event

Здесь нужно сделать оговорку, я хотел написать, что только с помощью event можно узнать какой именно элемент инициировал то или иное событие, но одно из сообщений Gilmour'а в форуме (а именно это) меня заставило задуматься так ли это.

Итак, используйте как можно чаще указателя this не только, когда перехватываете событие с помощью атрибутов, но также, если перехватываете их с помощью скрипта — он всегда указывает на объект, для которого осуществляли перехват (в отличие от srcElement в MSIE и target в NN).

А теперь вернемся к тому, каким образом получить этот самый event. Различия следующие — в NN4+/Mozilla объект передается как первый и единственный параметр обработчика, в MSIE и Opera нет вообще параметров, а есть объект window.event, который свой для каждой функцией. Поэтому прежде, чем пользоваться нужно просто сделать указатель на него которым и пользоваться:

function clickElem(evt) {
   e = (evt)? evt : window.event;
   alert(e.type);
}

Данная функция просто покажет сообщение со словом «click».

А какие именно свойства объекта event вам будут нужны уже зависить от скрипта, который пишете. Подробнее о разных свойствах если и буду писать, то только не в этой статье.

Добавление обработчиков

В самом начале статьи я упомянул, что помимо перехвата событий с помощью определенных свойств, можно воспользоваться еще и специальных методов, чтобы добавлять обработчиков. Плюсы состоят в том, что таким образом можно добавить не один обработчик для конкретного события у конкретного элемента, а неопределенное количество. Минусы — работает это только в MSIE5+ и NN6/Mozilla и при этом методы отличаются сильно.

Для начала — зачем это нужно? Предположим, нужно обработать событие для какого-то элемента на странице, у которой подгружаются большое число внешних скриптов и очень не хочеться просматривать всех, чтобы выяснить не перехватывает ли кто-нибудь еще это событие. Можно конечно просто «спросить» не определено ли такое-то свойство, но если определено, то что? Переделывать обработчик «на лету»? Никому не нужны такие сложности, именно поэтому проще всего добавить еще один обработчик, который будет выполняться паралельно с уже существующем.

Какой способ предлагает Microsoft. В MSIE5 появились два метода у практически всех элементов: attachEvent() и detachEvent(). Первый — чтобы добавить обработчик, второй, чтобы убрать его. У обоих есть два параметра — событие и функция-обработчик:

elementPtr.attachEvent('onclick', clickElem);

В качестве перехватываемого события нужно использовать строку из одного слова — такое-же, какое свойство использовали бы при стандартном перехвате. Обработчик — это только имя функции. Чтобы прекратить перехват нужно вызвать другого метода с точно такими-же параметрами:

elementPtr.detachEvent('onclick', clickElem);

Все просто и понятно (я надеюсь), никаких проблем.

В своей рекомендацией по DOM2 Events, W3C однако предлагает другое решение. Тоже два метода: addEventListener() и removeEventListener(). Именно их и использует NN6/Mozilla (наверное следует начать писать просто Gecko, только не знаю насколько понятно для вас будет). У них не два, а три параметра. Третий — это булевая переменная (true или false), которая указывает запускать ли обработчики событий данного типа у «детей» элемента или нет. Т. е. если true, то все обработчики этого-же события для элементов, находящихся выше (ниже?) по дереву не будут обрабатываться — запуститься только данный обработчик.

Есть и еще одно отличие — строка, которая определяет событие должна быть такая-же какая возвращает свойство event.type, т. е. без префикса «on»:

elementPtr.addEventListener('click', clickElem, false);

Чтобы убрать обработчик, как и в случае с MSIE нужно просто использовать те-же самые параметры:

elementPtr.removeEventListener('click', clickElem, false);

Тоже все просто, может быть немного запутано объяснил значение последнего параметра, но я вернусь к нему и ко всей темы event capturing в другой раз. Пока достаточно сказать, что в MSIE по умолчанию события передаются детям, поэтому можно сделать следующие кросс-браузерные функции:

function addEvent(elementPtr, eventType, eventFunc) {
   if (elementPtr.addEventListener) {
      elementPtr.addEventListener(eventType, eventFunc, false);
   } if (elementPtr.attachEvent) {
      elementPtr.attachEvent('on' + eventType, eventFunc);
   } else {
      // что делать если ни то ни другое не поддерживается<br />
   }
}

function removeEvent(elementPtr, eventType, eventFunc) {
   if (elementPtr.removeEventListener) {
      elementPtr.removeEventListener(eventType, eventFunc, false);
   } if (elementPtr.detachEvent) {
      elementPtr.detachEvent('on' + eventType, eventFunc);
   } else {
      // что делать если ни то ни другое не поддерживается
   }
}

Это примерные функции и если действительно собираетесь использовать что-то подобное, но наверняка нужно все-таки передавать третий параметр и учитывать то, что в MSIE attachEvent возвращает true или false в зависимости от того было ли добавлено событие или нет.

Еще я где-то видел функции, которые добавляют MSIE-функции в Gecko, чтобы использовать только attach- и detachEvent, но к сожалению сейчас их не нашел.

Когда и что стоит использовать

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

Однако, если перехватываете события для какого-то конкретного элемента, или обработка слишком разная, то уже преимущества перехвата скриптом уже не такие явные.

Еще у перехвата скриптом есть недостаток (который иногда может быть и преимуществом). Когда назначаются обработчики с помощью атрибутов они начинают работать сразу после загрузки конкретного элемента. Перехват скриптом следует делать после полной загрузки всего документа или хотя бы после загрузки конкретного элемента, в противном случае элемент просто не будет найден и... обработчик не будет назначен.

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

Обсудить в форуме

Еще про событий

  1. События для начинающих
  2. Перехват события в HTML'е
  3. Перехват событий скриптом