jQuery

индекс
283,92

Пример использования пользовательских событий

Наверное много кто знает что в jQuery есть набор стандартных событий, таких как Click или MouseDown и прочие, на которые можно повесить обработчики или возбудить с помошью функций click() mousedown() и прочих. Чуть поменьше людей знают, что те-же самые действия можно сделать с помощью функций bind() и trigger():
$(document).bind('click', function(){
  alert('It works!');
});

$(document).trigger('click');


* This source code was highlighted with Source Code Highlighter.

И наверное мало кто знает, что в функциях bind() и trigger() можно использовать свои собственные события. Зачем это нужно, я и хочу рассказать на примере.

Постановка проблемы

Представим, что у нас на сайте полно всяких всплывающих окон, причем вызываются они из разных мест, какие-то всплывают с информацией о товаре, какие-то с формой логина, какие-то еще где-то. Первая мысль о том, как это реализовать, наверное, будет такой:
$('#products .item .info').bind('click', function(){
  $(this).parents('.item').eq(0).find('.float').fadeIn(500);
});

$('#login-link').bind('click', function(){
  $('#login-form').fadeIn(500);
});


* This source code was highlighted with Source Code Highlighter.

Мысль эта заведомо неправильная, и вы это поймете, как только дизайнер скажет «мне кажется что лучше бы они появлялись не за 0,5 секунд, а за 2» или «я имел ввиду, что оно должно вылетать слева, а не справа» и вам придется искать и править все места, де у вас появляется это чертово окошко.

Второй вариант, как это реализовать, наверное будет примерно такой:
function ShowMe(el) {
  el.fadeIn(500);
}

$('#products .item .info').bind('click', function(){
  ShowMe($(this).parents('.item').eq(0).find('.float'));
});

$('#login-link').bind('click', function(){
  ShowMe($('#login-form'));
});


* This source code was highlighted with Source Code Highlighter.

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

У обоих методов есть дополнительная общая проблема: помимо появления окошка, нужно еще позаботиться о его скрытии, которое, во первых, тоже может быть анимированным, а во вторых, очень часто после закрытия окна нужно сделать какие-то дополнительные действия, которые вам тоже придется как-то учитывать при каждом способе закрытия окна (а способов можно придумать сколько угодно, крестик, кнопка «закрыть», ссылка «закрыть» в тексте окна, нажатие вне окна).

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

Используем пользовательские события

Я подготовил небольшой пример, в котором как раз представлены 2 подхода. Код первого, без использования событий, находится в блоке <div id="not-envents">. Второй, соответственно, в <div id="envents">.
Что я хотел показать первым примером:
  1. Смешано то, что называется «логикой» и «представлением», в одном месте определяется что должно происходить и как это должно делаться
  2. Когда понадобилось сделать .another-link1 (который гипотетически находится вообще в другом файле, просто должен открывать то же самое окно), пришлось повторить весь код, отвечающий за показ
  3. Когда понадобилось сделать ссылку в «Window 2», закрывающую окно прямо в тексте, пришлось повторить код, отвечающий за скрытие. Причем это простейший случай, все могло быть намного хуже.
Во втором примере я использую пользовательские события «hidefloat» и «showfloat». Что я хотел показать вторым примером:
  1. Можно выделить какие-то общие элементы для всех всплывающих окон и написать их логику в одном месте, что и показано на примере кнопки .close
  2. «Представление» (та часть, которая говорит как именно должно прятаться окно) полностью отделено от логики. Причем, конечно, название «представление» тут более чем условное. Очень часто может понадобиться при появлении окна показывать какие-то дополнительные элементы, а при скрытии прятать.
  3. Все события, навешанные на элементы (.link1, .link2, .link3) четко говорят, что нужно делать, но ничего не говорят о том, как это нужно делать.
  4. Код ссылки в тексте в «Window 2» также говорит только то, что окно должно быть спрятано. Если то, каким образом оно должно прятаться, изменится, нам не придется править ссылку в «Window 2».
  5. Наконец, становиться возможным сделать ссылку «.closeall», которая также ничего не знает о том, как будут прятаться окна. Даже на такой простой страничке видно, что создание кнопки «.closeall» для первого варианта — что-то из разряда фантастики.

Естественно, все что я описал, это только пример того, какие проблемы могут решить пользовательские события, на самом деле область их применения не ограничивается одним всплывающим окном.
+39
16 августа 2009, 12:08
113

комментарии (29)

+3
Alexander_N #
Отличные примеры. Неплохо было бы увидеть в статье код скриптов, немого неудобно его вылавливать в страницах, тем более что на той страничке с примером подгружается еще несклько js файлов.
0
homm #
Только jQuery. Весь остальной код и даже картинки внутри html.
0
xzander #
ну, например, такое делать не обязательно:
Когда понадобилось сделать .another-link1 (который гипотетически находится вообще в другом файле, просто должен открывать тоде самое окно), пришлось повторить весь код, отвечающий за показ

можно было просто добавить через запятую селектор в первый вызов:
$('#not-envents .link1, #not-envents .another-link1')

и гораздо интереснее был бы вариант сравнения подхода trigger и обычных функций
0
homm #
можно было просто добавить через запятую селектор в первый вызов:
Я же как раз написал, что описание поведения элементов, которые должны открывать один и тот-же блок (имеются ввиду .link1 и .another-link1) может быть разнесено по разным js-файлам, или по крайней мере по разным логическим местам одного файла (например, .link1 это ссылка в сайдбаре, а .another-link1 — на главной, описание их одним селектором явно не добавит коду читаемости).
–1
vodka_ru #
Слово «не правильная» пишется слитно, а само направление мыслей правильное ;)
0
Ewg #
Автор пытается сказать, что никто не использует функции, а он такой правильный…
0
hTLK #
А как быть, если надо в обработчик пользовательского события передать какие-то дополнительные данные? Это я спрашиваю к тому, что обычно юзаю навешиваю свои функции прямо
0
hTLK #
Случайно нажалось написать :(
0
hTLK #
Я имел в виду, что навешиваю свои функции прямо на нужные мне DOM элементы и потом работаю именно с ними. Получается что div — это не просто div, а некое объектное представление всплывающего окошка с функциями вроде Show и Hide.
0
homm #
Если вы работаете без использования фреймворков, такой подход равнозначен описанному мной. Если работаете с jQuery, всетаки его селекторы и прочие вкусности использовать удобнее.
+1
homm #
Без проблем.
$("p").click( function (event, a, b) {
 // when a normal click fires, a and b are undefined
 // for a trigger like below a refers to "foo" and b refers to "bar"
} ).trigger("click", ["foo", "bar"]);


* This source code was highlighted with Source Code Highlighter.
+1
hTLK #
Вот это меня и интересовало, спасибо :) Недавно просто начал переходить на jQuery.
0
kachkaev #
Извините за замечание, но, возможно, следовало бы проверить орфографию Ваших комментариев на английском… Ну, или хотя бы на русском написали их:)
+2
Wott #
Супер!

я бы для линков повесил атрибут name в котором бы указал класс или ID связанного div-а. И bind выглядел бы попроще и универсальнее:

<a href="#" class="link" name="window1">show 1</a>
<a href="#" class="link" name="window2">show 2</a>

...

$('#envents .link').click(function(){
  $('#envents .'+$(this).attr('name')).trigger('showfloat');
  return false;
})


* This source code was highlighted with Source Code Highlighter.

0
funca #
Для целеуказания по id лучше использовать a.href.

<a class="link" href="#target">click me</a>

<div id="target"></div>
...
$('.link').click(function() {
  $(this).attr('href').trigger('showFloat');
  return false;
}}
+1
homm #
Если продолжить тему, то нужно сделать нормальный переход на якорь #target, и с какой-то периодичностью проверять значение якоря в адресе. Тогда, если скинуть другому пользователю ссылку с якорем, он тоже увидит открытое окошко. Это еще называют Ajax-навигацией.
0
consolamentor #
А как сделать, чтобы окно не прыгало к этому якорю (или вверх окна)?
Я не специалист в js, но видимо здесь нужно использовать event.preventDefault()? Не отменит ли это и вставку якоря в адрес?
Также, при таком способе, как я понимаю, нельзя передать дополнительные параметры (как просят сверху ↑).
0
consolamentor #
Ссылка не сработала: habrahabr.ru/blogs/jquery/67185/#comment_1896788
0
homm #
А как сделать, чтобы окно не прыгало к этому якорю?
Нужно чтобы на странице не такого якоря. Текстовые параметры передаются в этой же строке. С объектами сложнее.
0
consolamentor #
Если сделать ссылку на несуществующий якорь, то браузер прыгнет в топ, или к самой ссылке.
0
Alexander_N #
Люблю такие статьи! Вроде ничего нового для себя не узнал, казалось бы, но повторение — мать учения. Плюс статьи в том что добавляет прозрачность в код и выстраивается четкая, понятная стуктура.
Bind и trigger сам использую давно. Помню один раз очень мне помогли, сами знаете ie на что только ни способен лишь бы работать неправильно. Вобщем дело было в следующем аяксовой форме клик по лейблу то ли не срабатывал, то ли не запускал нужное действие (сейчас точно уже не помню), так вот bind и trigger все вернули в нужное русло.
0
funca #
Вот за что я не люблю jQuery, так это за то как, он относится к пользовательским событиям. Пример:
$().bind('test', function() { window.console.log('test1') })
$().bind('test', function() { window.console.log('test2'); throw 'Error!'; })
$().bind('test', function() { window.console.log('test3') })
$().trigger('test');
...
test1
test2
Error!

'test3' нет. Т.е. если какой-то обработчик обломался и бросил исключение, то обработчики, стоящие в цепочке после него, событие не получат.

C практической точки зрения, события используются в том случае, когда необходимо синхронизировать работу ряда _независимых_ приложений. Поэтому глюки отдельного обработчика не должны влиять на диспетчеризацию события (если только он явно не вызвал event.stopImmediatePropagation()). Это же не поток.

В других фреймворках (том же prototype.js) такого глюка нет.
0
homm #
Я считаю, тут нужно смотреть не на поведение других фреймворков, а на поведение чистого JS+DOM. Если в нем исключение не останавливает цепочки выполнения, нужно слать багрепорт разработчикам jQuery.
0
funca #
Вот так, через дом2:
<a id='target'>click me</a>

$('#target').get(0).addEventListener('click', function(e) { window.console.log('test1') }, false)
$('#target').get(0).addEventListener('click', function(e) { window.console.log('test2'); throw "Error1"; }, false)
$('#target').get(0).addEventListener('click', function(e) { window.console.log('test3') }, false)

...
test1
test2
test3
Error!

–1
shutnik #
ждём ещё про jQuery ;)
–1
django #
А чего ждать, все тонны остальных статей, что гугл выдаст, уже прочитали?
+1
kulakowka #
ОТличный пример! а я то думал как мне блин закрыть каждый экземпляр всплывающего окна… а тут все просто. дернул событие для «дива» :) он и скрылся. Элегантно — респект! Отблагодарил в карму.
0
ksupipr #
Пишем в данный момент браузерку, использую JQuery, весьма помогает подобная работа с событиями.

Еще весьма интересная тема по поводу перетаскивания объектов. Этоя в плане хотелось бы подобную статейку. )
0
nicothin #
… и если понадобиться…

«ь» лишний.

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