Практически все JavaScript сценарии используют в своей работе события. Это обусловлено тем, что именно события связывают действия происходящие в документе, с соответствующим кодом JavaScript, тем самым обеспечивая динамику веб-интерфейса.
Обычно обрабатываются события порожденные действиями пользователя, но они могут возникать без его прямого участия, например событие load у некоторых элементов HTML вызывается в момент окончания загрузки этого элемента.
Всего существует много событий, некоторые из которых могут быть обработаны практически на любом элементе HTML, другие - специфические для опеределенных элементов, или объектов JavaScript.
Все события можно условно разделить на следующие типы:
События DOM-элементов, которые инициируются действиями пользователя. Например click - клик на элементе, или mouseover - мышь появилась над элементом.
События DOM-элементов или объектов JavaScript, которые инициируются в ходе их работы. Например load, readystatechange и т.п.
События для окна. Например resize при изменении размера и move при перемещении окна браузера.
Обработчик события можно указать в виде inline-записи, прямо в атрибуте тага onимя_события. Например, чтобы отслеживать нажатия на кнопку input, можно назначить обработчик onclick вот так:
Таким образом можно установить обработчик любого события, используя атрибут onимя_события="код_обработчика" того тага, на котором это событие должно быть обработано.
Стоит обратить внимание, что имя события в атрибуте нужно указывать с префиксом "on", хотя по рекомендации W3C имена событий не содержат этого префикса. Другими словами, для события click используется атрибут onclick, для mouseover – onmouseover, и т.д.
Напомним, что имена атрибутов HTML тагов нечувствительны к регистру, поэтому oNcLiCk сработает так же, как onClick и onclick. Но если вы на своих страницах используете спецификацию XHTML, то имена тагов и их атрибуты должны быть указаны в нижнем регистре, для совместимости с XML.
Это самый простой, старый, но в тоже время самый наглядный способ установки обработчика, который будет работать в любом браузере с поддержкой JavaScript.
Тем не менее, этот способ имеет свои минусы, самым большим из которых является сложность определения контекста события. Например, если вам нужно узнать на каком элементе сработало событие, то в обработчике вы обычно используете ссылку this, но при использовании inline записи обработчика, this будет ссылаться на элемент только внутри значения атрибута, но не в вызванных из него функциях.
Это происходит потому, что фактически обработчиком выступает код, указаный в атрибуте, а не функции, которые в нем вызываются.
Другими словами, вам нужно будет передавать в вызываемые в обработчике функции ссылку на текущий элемент самостоятельно. Подробнее об этом будет написано в разделе "Определение контекста события".
Еще один минус заключается в том, что если в документе много элементов, на которые таким способом установлен обработчик, или установлены обработчики на несколько событий одного элемента, то верстка HTML "захламляется" этими вызовами.
По возможности, лучше полностью разделять код сценариев и верстку HTML, поэтому рекомендуется устанавливать обработчики напрямую из JavaScript.
Самый "близкий родственник" представленного выше способа, это установка функции-обработчика в свойство onимя_события соответствующего элемента. Этот способ тоже будет работать в любом браузере с поддержкой JavaScript (и DOM, в конкретных случаях использования).
Стоит сразу обратить внимание на две детали:
Это именно свойство, а не атрибут. Поэтому, хотя технически и есть кроссбраузерные способы назначать обработчики через setAttribute, но лучше их даже не знать, а пользоваться прямым присвоением.
Кроме того, как и все свойства объектов JavaScript, имя свойства onимя_события чувствительно к регистру символов и должно быть всегда быть в нижнем регистре.
Обработчик - не текст, а именно функция javascript.
При первом способе, т.е когда обработчик прописан напрямую в HTML, браузер автоматом создает из него функцию.
Вот пример установки обработчика события click на элемент документа:
В этом примере в качестве обработчика использовано анонимное замыкание, но вы также можете установить любую объявленную функцию:
function doSomething() {
alert('Спасибо');
}
document.getElementById('b1').onclick = doSomething;
Обратите внимание, что не нужно указывать круглые скобки при присваивании, иначе функция будет выполнена не при соответствующем событии, а сразу же, и свойству будет присвоен её результат.
У прямого объявления через свойство есть недостаток - на элемент можно повесить только один обработчик одного события.
Зато есть и достоинства, например удобное определение условий события - в обработчике this всегда указывает на элемент, на котором сработало событие, а в качестве первого параметра обработчику передается объект Event (см. раздел "Оперделение контекста события").
Кроме того, существует возможность определить установлен ли обработчик на определённое событие элемента, и даже запустить обработчик самостоятельно из Javascript:
var input = document.getElementById('b1');
input.onclick = function() {
alert('Спасибо')
}
if (typeof input.onclick != "undefined") {
alert("На элемент было установлено событие!");
// Запускаем обработчик напрямую:
input.onclick();
}
Такой способ установки обработчика очень популярен и используется повсеместно, поскольку он работает во всех браузерах и имеет лишь один серьёзный недостаток.
Представленных выше методов иногда недостаточно для случаев, которые возникают при разработке более-менее серьёзного JavaScript приложения. Этими способами невозможно установить два (и больше) обработчика на одно событие одного элемента и невозможно удалить определённый обработчик из нескольких.
Такой функционал может понадобиться например тогда, когда на сайте присутствует несколько (или неопределённое количество) одновременно работающих скриптов, которые инициализируются по событию load документа.
Как всегда, не существует абсолютно кросс-браузерных методов по установке и удалению обработчиков событий, и их существует два варианта.
Методы, предложенные Microsoft, работают только в браузере Internet Explorer и имеют серьёзные недостатки, из-за которых их часто неудобно использовать даже в нем.
Как видно из примера, установка обработчиков происходит не сложнее, чем в тех способах, о которых рассказывалось ранее. Тем не менее, существует несколько очевидных (и не очень) недостатков, из-за которых часто не получается использовать решение от Microsoft.
Самым большим недостатком является то, что этот метод вызывает обработчик по указателю, а не копирует его, как все остальные методы, поэтому this внутри обработчика всегда указывает на объект window и совершенно бесполезен.
Кроме того, обработчику не передается currentTarget, а это значит, что в отличие от других методов, невозможно определить на каком элементе сработало событие при "всплывании".
Из-за того, что Internet Explorer поддерживает только "всплывающий" порядок срабатывания событий на вложенных элементах, attachEvent не может обрабатвать событие на фазе "Перехвата" (подробнее об этом в разделе "Порядок срабатывания событий").
Из-за своих недостатков, решение от Microsoft не отвечает требованиям, предъявляемым таким методам, и не является аналогичной альтернативой решению от W3C.
Лучше отказаться от его использования в пользу эмуляции функционала решения W3C, которая используется в кросс-браузерных реализациях установки обработчиков, одна из которых представлена в разделе "Кросс-браузерное управление событиями" этой статьи.
Этот метод может работать с любыми объектами, а не только элементами DOM, в отличии от свойства onимя_события. Такое применение часто используется при разработке плагинов для браузера, или в скриптах, которые взаимодействуют с объектами браузера.
Обратите внимание, что имя события указывается без каких-либо префиксов, соответствуя рекомендациям W3C.
Еще одно отличие от решения Microsoft это третий параметр – фаза.
Если он установлен в true, то при срабатывании события во вложенном элементе, обработчик будет вызван на фазе "перехвата", а если значение будет false, то - на фазе "вслывания". Подробнее об этом написано в разделе этой статьи «Порядок срабатывания событий».
Для обычной установки обработчика третий параметр всегда должен быть false.
Как и в других случаях, вы должны передать имя обработчика не ставя круглых скобок, иначе функция будет выполнена, а в качестве обработчика будет передан её результат.
Параметры функций в случае должны быть одинаковыми при установке и снятии обработчика. И сразу нужно отметить, что в метод removeEventListener должен быть передан тот же обработчик, который был установлен в функции addEventListener и это накладывает определённые ограничения в использовании анонимных функций в качестве обработчиков.
Есть еще одна проблема, с которой можно столкнуться при управлении событиями: нельзя точно сказать установлен ли определённый обработчик, или нет, и какие обработчики установлены на данный момент.
В спецификации DOM 3 существует объект eventListenerList, но он слишком новый и на данный момент не поддерживается ни одним из браузеров. Тем не менее, вы можете всегда смело вызывать removeEventListener даже для тех обработчиков, на счет которых вы не уверены, были ли они установлены или нет, т.к. это не вызовет ошибки.
Важно учитывать, что порядок срабатывания обработчиков не описан в спецификации, другими словами, в случае:
полагаться на то, что doSomething1 сработает раньше, чем doSomething2 не стоит, хотя это часто происходит именно так.
Еще вы можете столкнуться с задачей, где вам нужно будет установить определённые обработчики в цикле. Если вы попробуете сделать вот так:
var element = document.getElementById("testEvents");
for (var i = 1; i <= 100; i++) {
element.addEventListener("click", eventHandler, false);
}
То обнаружите, что будет установлен только один обработчик. Решением этой проблемы может быть установка обработчика с небольшим тайм-аутом:
var element = document.getElementById("testEvents");
for (var i = 1; i <= 100; i++) {
setTimeout( function() {
element.addEventListener("click", eventHandler, false);
}, 1); // 1 мс должно хватить.
}
В заключение разговора о addEventListener/removeEventListener можно сказать, что несмотря на небольшие недостатки, использовать эти методы удобно. При работе с ними не появляется проблем с определением контекста, и можно установить обработчик, который будет срабатывать только на определённой фазе цепочки событий.
Этот код создан Dead Edwards и модифицирован Tino Zijdel.
Эти функции реализуют решения от W3C на всех тех браузерах, где отсутствуют addEventListener/removeEventListener, включая Internet Explorer.
Это реализуется через цепочку вложенных обработчиков на свойстве onимя_события.
Заодно, этот способ управления событиями добавляет IE-событиям W3C методы preventDefault и stopPropagation.
// written by Dean Edwards, 2005
// with input from Tino Zijdel - crisp@xs4all.nl
// http://dean.edwards.name/weblog/2005/10/add-event/
function addEvent(element, type, handler)
{
if (element.addEventListener)
element.addEventListener(type, handler, false);
else
{
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
if (!element.events) element.events = {};
var handlers = element.events[type];
if (!handlers)
{
handlers = element.events[type] = {};
if (element['on' + type]) handlers[0] = element['on' + type];
element['on' + type] = handleEvent;
}
handlers[handler.$$guid] = handler;
}
}
addEvent.guid = 1;
function removeEvent(element, type, handler)
{
if (element.removeEventListener)
element.removeEventListener(type, handler, false);
else if (element.events && element.events[type] && handler.$$guid)
delete element.events[type][handler.$$guid];
}
function handleEvent(event)
{
event = event || fixEvent(window.event);
var returnValue = true;
var handlers = this.events[event.type];
for (var i in handlers)
{
if (!Object.prototype[i])
{
this.$$handler = handlers[i];
if (this.$$handler(event) === false) returnValue = false;
}
}
if (this.$$handler) this.$$handler = null;
return returnValue;
}
function fixEvent(event)
{
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
return event;
}
fixEvent.preventDefault = function()
{
this.returnValue = false;
}
fixEvent.stopPropagation = function()
{
this.cancelBubble = true;
}
// Этот фрагмент исправляет проблему, когда onload-атрибут
// элемента body перезаписывает назначенные до этого обработчики
// события window.onload
if (!window.addEventListener) {
document.onreadystatechange = function() {
if (window.onload && window.onload != handleEvent) {
addEvent(window, 'load', window.onload);
window.onload = handleEvent;
}
}
}
На одно событие могут реагировать не только тот элемент, на котором произошло событие, но и элементы над ним.
Рассмотрим ситуацию, когда у вас есть три элемента "вложенных" друг в друга.
1
2
3
<div class="d1" >1<!-- самый верхний, в представлении DOM, элемент -->
<div class="d2">2
<div class="d3">3</div><!-- самый глубокий элемент -->
</div>
</div>
Если они будут обрабатывать одно событие, то обработчик для какого элемента будет вызван первым? Всего существует 2 модели поведения, они не имеют преимуществ между собой, но используют принципиально разные подходы. Стандарт W3C объединяет две модели в одну универсальную.
В этой модели сначала будет выполнен обработчик на элементе 1, и последним будет выполнен обработчик на элементе 3. Она называется "перехват", потому что родительские элементы могут обработать событие раньше, чем непосредственная цель события, как бы "перехватывая" обработку.
Визуально это выглядит так (кликните на вложенном элементе, чтоб увидеть, какой будет порядок обработки события):
Такой порядок был предложен Netscape и никогда не поддерживался в Internet Explorer, поэтому в IE вы не сможете увидеть этот пример в действии. Остальные браузеры поддерживают одновременно такой порядок и порядок "всплывания".
В этой модели сначала будет выполнен обработчик на элементе 3, и последним будет выполнен обработчик на элементе 1. Такой порядок называется "всплывающим", потому что событие поднимается с самых "глубоких" элементов в представлении DOM, к самым "верхним", как пузырек воздуха в воде.
Визуально это выглядит так (кликните на вложенном элементе, чтоб увидеть, какой будет порядок обработки события):
Нужно понимать, что "всплывание" происходит всегда. При возникновении события на элементе, событие будет подниматься до самого высокого элемента выполняя нужные обработчики.
Если такой функционал не нужен, от него можно избавиться, использовав в одном из обработчиков следующую конструкцию:
// Вариант Internet Explorer:
event.cancelBubble = true;
// Вариант стандарта W3C:
event.stopPropagation();
После выполнения обработчика, который содержит такой код, событие прекратит "всплывать". Нужно отметить, что "всплывание" не имеет ничего общего с "действием по-умолчанию", поэтому такой код, например, не отменит действие перенаправления по ссылке, по щелчку на ней.
Во время "всплывания", каждый обработчик имеет доступ к элементу, который стал целью события, и к текущему элементу. Это не относится к обработчикам, установленым через attachEvent/detachEvent - они в такой ситуации остаются слепы. Подробнее об этом в следующем разделе "Определение контекста события".
Решение от W3C объединяет обе модели в одну универсальную.
При совершении действия, сначала события будут "перехватываться", пока не достигнут конечного элемента, затем опять "всплывать". Таким образом, разработчик сам решает, когда должен срабатывать обработчик события – при "перехвате", или при "всплывании".
Визуально это выглядит так (кликните на вложенном элементе, чтоб увидеть, какой будет порядок обработки события):
Как уже было написано ранее, если в качестве третьего параметра функции addEventListener передать значение true, то событие будет срабатывать при первой фазе "захвата", если false – при фазе "всплывания".
При установке обработчиков классическими методами (через свойство элемента или атрибут html тага) события всегда срабатывают на фазе "всплывания".
Ключевое слово this в обработчике события означает текущий элемент.
По мере всплытия, текущим элементом каждый раз становится новый. Например, при клике на внутренний div этот пример последовательно выводит элементы, на которых регистрируется всплывающее событие:
function() {
// получить объект событие.
// вместо event лучше писать window.event
e = arguments[0] || event
// кросс-браузерно получить target
t = e.target || e.srcElement
alert(t.className)
}
function handler() {
var event = arguments[0] || window.event
...
}
Под словом «handler» обычно понимается объект, который вызвал это событие, т.е. «target» или «srcElement» (в IE), а здесь мы получаем объект «событие», содержащее этот элемент. Мне кажется будет удобнее записать в таком виде:
Попробуйте jQuery использовать, там как раз есть trigger/triggerHandler.
Если без jQuery - в браузерах есть метод fireEvent/dispatchEvent специальный. Названия методов - приблизительны, в IE там одно а в других браузерах - по стандарту.
Классный пример с имитацией эвэнтов, но есть трабл, не вешается на инпуты типа file и submit, на батон вешается а на этих двоих нет. Может есть какое решение?
Обратите внимание, что не нужно указывать круглые скобки при присваивании, иначе функция будет выполнена не при соответствующем событии, а сразу же, и свойству будет присвоен её результат."
А что делать, если необходимо вызвать функцию с параметрами?
Если поставить фокус в текстовую область, а потом нажать "Отправить", то сработает онБлур, а онКлик проигнорируется. Как можно сделать так, чтобы одним щелчком сработали оба события? И если это возможно, чтобы сработал сначала онКлик, а потом онБлур. Попытался использовать примеры из статьи, так и не понял, как сделать так чтобы работал режим Всплывания, а не Перехвата, потому что по умолчанию всегда именно Перехват. Заранее благодарю.
На элемент li повешены события onmouseover и onmouseout, которые, скажем, меняют класс самого элемента - устанавливает и снимает соответственно (кстати, спасибо за статью - понял, как сделать кроссбраузерное решение через проверку srcElement). Но при наведении курсора на вложенную ссылку, видимо, срабатывает onmouseout - и у LI снимается установленный ранее класс. Как обойти эту ситуацию? - чтобы при наведении на ссылку не пропадал эффект onmouseover элемента LI.
Нашел недочет в коде - достаточно было браться не за event.target, а за event.currentTarget
И вообще, как оказалось, лучше было его за объект браться как this - и с кроссбраузерностью не было бы мороки
Андрей Параничев,
Использовал я function addEvent(element, type, handler) (описанную здесь Вами) года два, свято веря, что автор всё предусмотрел и отладил. Однако, бывает такое, что бяки вылезают и через годы, причем в самых неожиданных местах. В одном из проектов потребовалось совместить перемещение объекта мышью с нажатой клавишей Ctrl. Так вот оказалось, что в IE при этом наблюдается РЕЗКОЕ ЗАМЕДЛЕНИЕ движения мыши (?!!). Исследования причин привели к Вашей функции addEvent()... Вот так вот.
«addEventListener» и «removeEventListener» не работают без указания 3-го параметра.
Спасибо за замечание. Многое сейчас дописывается и будет исправлено, это лишь черновик.
Под словом «handler» обычно понимается объект, который вызвал это событие, т.е. «target» или «srcElement» (в IE), а здесь мы получаем объект «событие», содержащее этот элемент. Мне кажется будет удобнее записать в таком виде:
Теперь, чтобы получить событие, вызвавшее функцию, нужно написать:
А для того, чтобы получить «handler»:
Ну это вводная статья, в ней будут только простые примеры.
Последний раздел будет сегодня вечером готов.
handler - обработчик.
Может проще все-таки так?
Скажите, а как можно сгенерировать событие для объекта?
Если имеется к примеру
то как объекту можно "вручную" послать notify
не работает
Объект Event тоже не содержит нужных методов, к тому же хочется кросс браузерное решение.
Попробуйте jQuery использовать, там как раз есть trigger/triggerHandler.
Если без jQuery - в браузерах есть метод fireEvent/dispatchEvent специальный. Названия методов - приблизительны, в IE там одно а в других браузерах - по стандарту.
Почитайте про методы document.createEventObject и fireEvent для Internet Explorer. И document.createEvent, initEvent, dispatchEvent для W3C-совместимых браузеров.
В вашем случае решение будет примерно такое:
Молодца. Огромное спасибо, особенно за наглядные примеры !!!
Классный пример с имитацией эвэнтов, но есть трабл, не вешается на инпуты типа file и submit, на батон вешается а на этих двоих нет. Может есть какое решение?
Всё вешается!!!
Спасибо за статью и за комментарии к ней
Очепятка в примере реализации MIcrosoft detachEvent (detachEvent назван detachElement'ом):
За что так Дена? Он вроде еще жив.
"function doSomething()
{
alert('Спасибо');
}
document.getElementById('b1').onclick = doSomething;
Обратите внимание, что не нужно указывать круглые скобки при присваивании, иначе функция будет выполнена не при соответствующем событии, а сразу же, и свойству будет присвоен её результат."
А что делать, если необходимо вызвать функцию с параметрами?
У меня есть такая "форма":
Если поставить фокус в текстовую область, а потом нажать "Отправить", то сработает онБлур, а онКлик проигнорируется. Как можно сделать так, чтобы одним щелчком сработали оба события? И если это возможно, чтобы сработал сначала онКлик, а потом онБлур. Попытался использовать примеры из статьи, так и не понял, как сделать так чтобы работал режим Всплывания, а не Перехвата, потому что по умолчанию всегда именно Перехват. Заранее благодарю.
Господа, подскажите, как поступить.
Есть HTML
На элемент li повешены события onmouseover и onmouseout, которые, скажем, меняют класс самого элемента - устанавливает и снимает соответственно (кстати, спасибо за статью - понял, как сделать кроссбраузерное решение через проверку srcElement). Но при наведении курсора на вложенную ссылку, видимо, срабатывает onmouseout - и у LI снимается установленный ранее класс. Как обойти эту ситуацию? - чтобы при наведении на ссылку не пропадал эффект onmouseover элемента LI.
Нашел недочет в коде - достаточно было браться не за event.target, а за event.currentTarget
И вообще, как оказалось, лучше было его за объект браться как this - и с кроссбраузерностью не было бы мороки
Андрей Параничев,
Использовал я function addEvent(element, type, handler) (описанную здесь Вами) года два, свято веря, что автор всё предусмотрел и отладил. Однако, бывает такое, что бяки вылезают и через годы, причем в самых неожиданных местах. В одном из проектов потребовалось совместить перемещение объекта мышью с нажатой клавишей Ctrl. Так вот оказалось, что в IE при этом наблюдается РЕЗКОЕ ЗАМЕДЛЕНИЕ движения мыши (?!!). Исследования причин привели к Вашей функции addEvent()... Вот так вот.