Порядок выполнения событий в DOM

Столкнулся с проблемой в своём календарике - есть два элемента, один из которых позиционируется абсолютно на весь экран, полупрозрачная затемняющая занавеска а второй - форма. Вы наверняка видели такие решения при показе картинок в lightbox или аутидентификации на habrahabr'е..

<div id='form_bg' onclick="$('form_bg').hide();$('form').hide();"> <div id='form'></div> </div>

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


Две с половиной модели

Как оказывается, существует две модели передачи событий в объектно-ориентированной иерархии

  • Пузырьковый метод (bubbling), когда событие возникает внутри и затем передаётся родительским элементам наружу. MS Internet Explorer, Opera, Firefox
  • Захват события (capturing), событие обрабатывается сначала у родителей, а потом проникает глубже. Opera, Firefox

Консорциум W3C благоразумно решили что разработчикам может быть удобно в любую сторону направлять события (event propagation), поэтому по стандарту две модели объединены - событие сначала захватывается, а потом возвращается как пузырёк.

Таким образом разработчик может привязать вызываемый метод к фазе события:

$('form_bg').addEventListener('click',hideBackground,true);
// true - говорит о фазе capturing

$('form').addEventListener('click',function(){},false);
// false - говорит о фазе bubbling

Получается что при клике на form_bg происходит сразу hideBackground, и form на фазе capturing не вызывается, затем возвращаясь, в фазе bubbling вызывается анонимная функция. 

Традиции по умолчанию

Модель W3C приятна, но по умолчанию по всей видимости из-за IE, обычная регистрация события подразумевает bubbling фазу, т.е. изнутри наружу. А это в свою очередь значит, что если я явно укажу на родительский элемент:

$('form_bg').onclick = hideBackground; // или как выше - в <div id='form_bg' onclick=..

То любое событие без остановки вызывает у всех родительских элементов обработку onclick-события. Есть у них оно или нет, вплоть до корня - document.

Заплыв без пузырьков

В кросс-браузерном варианте для остановки распространения обработки события к родительским элементам, надо поменять параметр cancelBubble на true (для IE) и вызывать функцию stopPropagation (W3C модель):

function hideBackground(e){
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}

Теперь мне интересно как устроена модель обработки событий в других языках и платформах - Java, NET, Flash..

Написано по мотивам статьи "Event order".

RSS

Комментарии

  • SHAman
    avatar
    Спасибо за статью. Полезно.
  • andead
    avatar
    спасибо!!! очень помагло при создании своего инлайн эдитора
  • Сергей
    avatar
    хотел написать в личку. не нашел как с тобой связаться
    в IE7 не отображается пример после фразы "метод к фазе события"
  • Спасибо, особо IE не жалую, подправил whitespace, контакты на главной странице
  • Сергей
    avatar
    такой же баг наблюдался на официальном сайте jquery. Жаль что разработчики работают в firefox и при этом забывают тестировать в IE

    и еще один баг в IE:
    кликаю на ответить - выдает script error
    firebug тоже ругается, но форму для ответа открывает
  • Octane
    avatar
    Очень удобно в этой ситуации использовать «prototype» встроенного конструктора объекта «Function».

    Например, реализация «preventDefault»:
    Function.prototype.preventDefault = function() { var ev = this.arguments[0] || window.event; ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; };
    тепер в любой функции, чтобы предотвратить стандартное действие, нужно написать такую строчку:
    arguments.callee.preventDefault();

    C «topPropagation» аналогично.