Ta strona używa ciasteczek (cookies), dzięki którym nasz serwis może działać lepiej. Dowiedz się więcej OK, rozumiem
WebHelp.pl Warsztat Artykuły jQuery - moduł zdarzeń

Warsztat / Artykuły i tutoriale

jQuery - moduł zdarzeń

Rafał Kukawski 13 maja 2012 komentarze ()

Jednym z najważniejszych elementów biblioteki jQuery jest moduł zdarzeń. Dlaczego jest taki ważny? Jak przedstawiłem w jednym z artykułów, obsługa zdarzeń w przeglądarkach jest mocno utrudniona przez bałagan, który powstał po wojnie konkurujących ze sobą producentów przeglądarek. jQuery cały ten bałagan ukrywa pod spójnym API, które sprawia, że obsługa zdarzeń staje się bajecznie prosta, oferując przy tym dodatkowe możliwości, których zaimplementowanie we własnym zakresie kosztowałoby sporo czasu i nerwów.

Jak tego używać?

Cała moc obsługi zdarzeń kryje się w dwóch funkcjach o nazwach on i off. Jak można zgadnąć, za pomocą pierwszej funkcji możemy „zarejestrować” funkcję obsługującą dane zdarzenie na wybranym elemencie, zaś drugą funkcją będziemy „wyrejestrowywać” event handlery.

Kod: Zaznacz cały
function clickHandler (event) {
   alert("Element został kliknięty");
   event.preventDefault();
}

$("button").on('click', clickHandler);

Powyższy kod pozwala za jednym zamachem przypisać nasłuchiwanie na kliknięcia do każdego przycisku na stronie.

Zarejestrowaną funkcję usuniemy poprzez wywołanie.

Kod: Zaznacz cały
$("button").off("click", clickHandler);

Jeśli jedna funkcja ma obsługiwać kilka rodzajów zdarzeń, można ją zarejestrować jednym wywołaniem, listując wszystkie zdarzenia oddzielone znakiem spacji.

Kod: Zaznacz cały
$("body").on("mouseover mouseout mousemove", function (event) {});

Dodatkowo istnieje możliwość przekazania mapy listującej wszystkie zdarzenia do zarejestrowania.

Kod: Zaznacz cały
$("form").on({
   "submit": submitHandler,
   "reset": resetHandler
});

Skróty

Do popularnych zdarzeń jQuery definiuje skrótowce pozwalające przypisać event handlery.

Kod: Zaznacz cały
$("button").click(clickHandler);
$("form").submit(submitHandler);

Wersja 1.7 zawiera skróty dla zdarzeń blur, change, click, dblclick, error, focus, focusin, focusout, hover, keydown, keypress, keyup, load, mousedown, mouseenter, mouseleave, mousemove, mouseout, mouseover, mouseup, resize, scroll, select, submit, unload.

Wyrejestrowywanie event handlerów raz jeszcze

W pierwszym rozdziale wspomniałem, żeby usunąć zarejestrowaną funkcję, należy wywołać off poniższym sposobem.

Kod: Zaznacz cały
$("button").off("click", clickHandler);

Jeśli chcielibyśmy usunąć wszystkie zarejestrowane event handlery, można sobie pomyśleć, że należy wywołać off dla każdej funkcji, co niesie ze sobą konieczność przechowywania referencji do tych funkcji w swojej aplikacji. Na szczęście jQuery umożliwia wykonanie tej czynności w dość prosty sposób – należy wywołać off tylko z jednym argumentem.

Kod: Zaznacz cały
$("button").off("click");

Przestrzenie nazw

Szczególnie interesującą dla autorów wtyczek funkcjonalnością są przestrzenie nazw zdarzeń. Przestrzenie nazw ułatwiają bowiem usuwanie oraz ręczne wywoływanie tylko określonych event handlerów.

Na czym to polega? Wyobraźmy sobie, że tworzymy wtyczkę skryptu lightbox. Gdy rejestrujemy zdarzenia kliknięcia na odnośnikach z atrybutem rel="lightbox", przypisujemy przestrzeń nazw poprzez podanie jej nazwy po nazwie zdarzenia i kropce.

Kod: Zaznacz cały
$("a[rel^='lightbox']").on("click.lightbox", clickHandler);

Nastepnie, gdy zajdzie potrzeba programistycznego wywołania tego zarejestrowanego event handlera (ale z pominięciem ewentualnych innych handlerów zdarzenia kliknięcia zarejestrowanych przez inne wtyczki i skrypty) wykonujemy polecenie

Kod: Zaznacz cały
$("a[rel^='lightbox']:eq(0)").trigger("click.lightbox");

W analogiczny sposób możemy wyrejestrować event handlery używane przez naszą wtyczkę.

Kod: Zaznacz cały
$("a[rel^='lightbox']").off("click.lightbox");

W ten sposób zagwarantujemy swoim użytkownikom, że nasz skrypt nie „zepsuje” innych skryptów na stronie.

Jednokrotne wywołanie event handlera (one)

Czasami zachodzi potrzeba zarejestrowania event handlera, który zostanie wywołany tylko raz. Przy kolejnych wystąpieniach danego zdarzenia funkcja będzie ignorowana.

Samodzielnie można ten efekt uzyskać poprzez wyrejestrowanie tej funkcji przez nią samą.

Kod: Zaznacz cały
$("button").on("click", function () {
    $(this).off("click", arguments.callee);

   // tutaj zaczyna się właściwa logika event handlera
});

Ale zamiast tego, możemy użyć specjalnej funkcji API jQuery – one, która wykona tę operację za nas.

Kod: Zaznacz cały
$("button").one("click", function () {
    // logika event handlera
});

ten sposób możemy się skupić na właściwej robocie i kod staje się czytelniejszy.

Przekazywanie danych do event handlerów

Dość często zachodzi potrzeba, żeby przekazać do danego event handlera pewnych danych, które znamy już w momencie ich rejestracji. Tutaj jQuery przychodzi z ratunkiem za pomocą 3 argumentu funkcji on.

Kod: Zaznacz cały
var dane = {
    hello: "World"
};

$("#button1").on("click", dane, function (event) {
    alert("Hello " + event.data.hello + "!");
});

W powyższym przykładzie, jako dane przekazywany jest obiekt. jQuery jednak nie ogranicza nas tylko do obiektów. Dana może być dowolnego typu za wyjątkiem undefined i null.

Czeka na nas jedna pułapka – gdy chcemy przekazać dane typu tekstowego, jQuery może uznać tą daną za selektor dla delegowanych eventów. Aby uniknąć niespodzianki, należy wywołać funkcję z drugim argumentem jako null, np. $("#button1").on("click", null, "World", clickHandler);

Delegacja zdarzeń

Wyżej opisane funkcje znacznie ułatwiają pracę ze zdarzeniami, ale i taj największa moc jQuery drzemie w delegowanych eventach.

Kiedy się przydaje delegacja zdarzeń?

Delegacja zdarzeń przydaje się najczęściej – co potwierdziła już spora ilość pytań na forach – gdy strona oparta jest mocno na JavaScripcie, a w szczególności ajaxie, gdzie treści są wczytywane dynamicznie.

Za przykład niech posłuży lista postów na Twitterze. Każdy z wpisów ma przyciski „Odpowiedź”, „Prześlij dalej”, „Ulubione”, itd. Kliknięcie w każdy z nich wywołuje pewną akcję.

Gdy przewiniemy stronę do dołu, odpowiednie skrypty doładują kolejne wpisy.

Gdyby zająć się sprawą w klasyczny sposób, musielibyśmy podczas załadowania strony podpiąć odpowiednie event handlery do istniejących elementów listy. Potem, gdy nowe treści zostaną dodane na koniec listy, musielibyśmy całą akcję powtórzyć dla nich. I tutaj rodzi się spory problem – taka lista może być teoretycznie nieskończona. Na każdy element listy trzeba zarejestrować przynajmniej 3 event handlery ze względu na 3 przyciski akcji. W ten sposób zaśmiecimy przeglądarkę setkami zarejestrowanych event handlerów, no i ciągle musimy pamiętać o elementach, które dynamicznie dodamy do listy.

Właśnie tutaj z pomocą przychodzą delegowane zdarzenia.

Na czym polega delegacja zdarzeń?

Cała idea polega na zarejestrowaniu danych zdarzeń gdzieś wyżej w hierarchii dokumentu – np. jeśli każdy tweet umieszczony jest wewnątrz elementu o class="tweet-list", można na tym właśnie elemencie nasłuchiwać na zdarzenia. Pamiętamy, że wiele zdarzeń przeglądarkowych bąbluje w górę.

Tak więc możemy dla 3 wymienionych wcześniej akcji zarejestrować handlery w następujący sposób

Kod: Zaznacz cały
$(".tweet-list").on("click", ".tweet > .reply", replyClickHandler);
$(".tweet-list").on("click", ".tweet > .retweet", retweetClickHandler);
$(".tweet-list").on("click", ".tweet > .favourite", favouriteClickHandler);

Proszę zauważyć drugi argument metody on, który jest selektorem do odpowiedniego przycisku w interfejsie aplikacji. jQuery będzie wychwytywał wszystkie kliknięcia wewnątrz listy tweet-list, ale zanim wywoła event handlera, sprawdzi, czy kliknięty został element pasujący do selektora z drugiego argumentu.

Ten sposób wywołania metody on ma też nieco inny wpływ na funkcję obsługującą zdarzenie. Podczas gdy wywołamy tę metodę bez dodatkowego selektora, this będzie wskazywać na .tweet-list, ale że mamy do czynienia z delegowanymi eventami, this będzie wskazywać na kliknięty przycisk, np. .reply.

W ten sposób możemy uzyskać pożądany efekt bez zaśmiecania pamięci setkami zarejestrowanych event handlerów.

I co najważniejsze, nawet jeśli listę uzupełnimy – np. poprzez żądanie ajaxowe do serwera – kolejnymi wpisami, nie musimy się martwić o obsługę zdarzeń na nowych elementach.

Co jeszcze „naprawia” jQuery?

Obiekt zdarzenia zgodny z modelem W3C

Najważniejszą naprawioną rzeczą jest wyeliminowanie różnic pomiędzy API Internet Explorera a tym zdefiniowanym przez W3C. Tak więc, w obiekcie zdarzenia możemy bez obaw używać event.target, event.stopPropagation() oraz event.preventDefault() i innych ustandaryzowanych pól i metod.

Emulacja niektórych zdarzeń

jQuery oddaje swoim użytkownikom emulację kilku zdarzeń, które niegdyś występowały tylko w wybranych przeglądarkach, ale były na tyle ważne, że postanowiono zbadać możliwości ich „emulacji”.

DOMContentLoaded

Z jednego z tych zdarzeń korzystamy praktycznie za każdym razem, gdy piszemy własne skrypty – jest nim $(document).ready(), które pozwala zaczekać naszym skryptom z inicjalizacją do czasu, gdy cała struktura DOM dokumentu zostanie wczytana i utworzona. Kiedyś tylko wybrane przeglądarki wspierały zdarzenie zwane DOMContentLoaded, które właśnie o tym informuje. Wykonano spory research, wiele eksperymentów, żeby korzystając z istniejących w przeglądarce zdarzeń, jak najbardziej zbliżyć się czasowo do momentu generowania DOMContentLoaded. Fallbackiem tego rozwiązania zawsze było zdarzenie load, gdy wszystkie inny próby zawiodły. Ale w grę wchodziło użycie też readystatechange oraz funkcji setTimeout, w której callback sprawdzał pewne cechy dokumentu, charakterystyczne dla pełnej struktury DOM.

Dzisiaj to zdarzenie, ta funkcja, ma w jQuery kilka aliasów.

Kod: Zaznacz cały
$(document).ready(callback);

$(callback);

$().ready(callback); // tej opcji osobiście nie lubię
mouseenter, mouseleave

Internet Explorer od wersji 5.5 obsługuje wspomniane w nagłówku zdarzenia. Są one praktycznie identyczne ze zdarzeniami mouseover i mouseout, z pewną ważną różnicą – te zdarzenia nie bąblują. W konsekwencji daje to różnicę taką, że krążenie kursorem po potomkach elementu na którym zarejestrowaliśmy zdarzenia mouseover i mouseout będzie generować wiele zdarzeń, ponieważ te będą bąblować od potomków do rodzica, zaś w przypadku zdarzeń mouseenter i mouseleave doświadczymy wystąpienia tych zdarzeń tylko gdy kompletnie wyjdziemy kursorem z obszaru tego elementu. Przesuwanie kursora po potomkach nie wygeneruje tych zdarzeń.

Zdarzenia mouseenter i mouseleave możemy obsłużyć rejestrując callbacki w tradycyjny sposób

Kod: Zaznacz cały
$("button").on("mouseenter", callback).on("mouseleave", callback2);

lub z użyciem aliasów

Kod: Zaznacz cały
$("button").mouseenter(callback).mouseleave(callback2);
focusin i focusout

W przypadku tych dwóch zdarzeń mamy odwrotną sytuację niż z mouseenter i mouseleave. focusin jest odpowiednikiem zdarzenia focus, z tą różnicą, że pierwsze bąbluje, drugie nie, co uniemożliwiało wydelegowanie zdarzenia.

focusout jest bąblującym odpowiednikiem zdarzenia blur. Obydwa zdarzenia natywnie obsługiwane przez Internet Explorera. W innych przeglądarkach zdarzenia występują albo pod innymi nazwami albo ich nie ma.

Rejestrujemy za pomocą on lub aliasów.

Kod: Zaznacz cały
$("input").on("focusin", focusInHandler)
    .on("focusout", focusoutHandler)
    .focusin(focusinHandler)
    .focusout(focusoutHandler);

Wyżej napisałem, że focus ze względu na brak bąblowania, nie może zostać wydelegowany. Jednakże jQuery, gdy spróbujemy wydelegować to zdarzenie, wewnętrznie zmapuje je do zdarzenia focusin, dlatego nic nie stoi na przeszkodzie, żeby z tego feature'a korzystać.

Wady

Implementacja modułu zdarzeniowego w jQuery to nie same zalety. Można wymienić kilka wad, jednak większość z nich wynika z przyjętych kompromisów niż z błędów popełnionych przez autorów biblioteki.

Największą wadą obsługi zdarzeń w jQuery jest, że nie obsłużony błąd w jednym z event handlerów będzie skutkował tym, że wszystkie pozostałe handlery czekające na wywołanie, nie zostaną wywołane.

Kod: Zaznacz cały
function first () { console.log("first"); }
function second () { throw new Error(); }
function third () { console.log("third"); }
function forth () { console.log("forth"); }

$("button").click(first).click(second).click(third).click(forth).click();

W powyższym kodzie, w konsoli zobaczymy tylko treść „first” i raport o błędzie.

Całego problemu możnaby uniknąć, gdyby jQuery wyłapywał błędy w zarejestrowanych handlerach. Dlaczego więc tego nie robi? Myślę, że z dwóch powodów. Po pierwsze wydajność — używanie try…catch w pętli jest dość kosztowne, po drugie — takie zachowanie jQuery utrudniłoby debugowanie zepsutych handlerów. Niestety, problem jest dość spory, ponieważ o ile możemy naprawić własne błędy, to humor mogą nam popsuć wtyczki innych autorów, które dołączymy do swojego projektu.

Drugim dość ważnym aspektem jest wydajność. jQuery oferuje rozbudowaną funkcjonalność obsługi zdarzeń, ale to niesie pewne problemy wydajnościowe. Ale nie ma się co przejmować na zapas, ponieważ w większości przypadków te problemy nie dotkną nas bezpośrednio, ponieważ dotyczą użycia specyficznych możliwości jQuery. Problematyczne mogą okazać się delegowane zdarzenia, które dość często są wyzwalane przez przeglądarkę, na przykład mousemove. Podczas delegowanych zdarzeń, jQuery musi za każdym wystąpieniem zdarzenia sprawdzić, czy nastąpiło na odpowiednim elemencie. Jeśli podany selektor jest czasochłonny do sprawdzenia, zauważymy „czkawkę” w działaniu przeglądarki.

Masz pytania lub wątpliwości? Odwiedź nasze forum dyskusyjne.

Rafał Kukawski

Programista, webmaster. Szczególnie upodobał sobie JavaScript i technologie klienckie, choć strona serwera i bazy danych nie stanowią tajemnicy. Tworzy też aplikacje na urządzenia mobilne. kukawski.pl.


Komentarze


HTML CSS JavaScript PHP bazy danych MySQL Flash grafika framework hosting domeny pozycjonowanie wordpress Facebook