• Strona główna
  • Curriculum Vitae
  • O mnie
  • Przykład: Gramatyka w PHP
  • Przykład: Kompresja CSS
  • Przykład: Kompresja JavaScript
  • Przykład: Skracanie linków
  • Przykład: Wykrywanie serwera HTTP
  • Przykład: Własna bramka SMS
  • Mapa strony
  • Kontakt
Niebieski Pomarańczowy Zielony Różowy Fioletowy

JavaScript sterowany zdarzeniami

Opublikowane 25 kwietnia 2013. Autor: Kamil Brenk. Wizyt: 2 992.

Kategorie: JavaScript
Tematyka: JavaScript, jQuery, przestrzeń nazw

kw. 25

Często słyszę oburzenie przeciwników JavaScriptu, jakoby przez wieczne funkcje zwrotne (callbacks) JavaScript był prymitywnym i strasznym językiem narzucającym trudne do opanowania zależności i zagnieżdżenia kodu.

Owszem, JavaScript ma wiele słabych stron i można mu sporo zarzucić, jednak sterowanie poprzez zdarzenia jest piękną cechą tego języka.

Często można trafić na kod początkujących programistów JavaScript, przypominający ten poniższy:

1
2
3
4
5
6
7
8
9
10
11
12
13
Lib('a').load(function() {
    Lib('b').load(function() {
        Lib('b').load(function() {

            form.submit(function() {
                submitViaAJAX(function() {
                    alert('Wysłałem!');
                });
            });

        });
    });
});

Zagnieżdżenia i problemy z callbackami w JavaScript to normalka i nieraz spotkałem się z jeszcze gorszymi strukturami. Co gorsze, kilka lat temu i mi zdarzało się tak pisać.

Z powyższym kodem można sobie poradzić na kilka sposobów, a poniżej omówię jedno z moich ulubionych podejść – sterowanie przez zdarzenia.

Obługa zdarzeń w JavaScript

W swoich przykładach posłużę się biblioteką jQuery, ponieważ na niej głównie pracuję. Poza tym ma wbudowany prosty, acz rozbudowany mechanizm obsługi zdarzeń.

Interesującą nas metodą jest znane wszystkim on służące do przypinania funkcji reagujących na dane zdarzenie, np.

1
jQuery(element).on('click', doSomething);

czy delegując zdarzenia także do dynamicznie tworzonych elementów:

1
jQuery(document).on('click', element, doSomething);

Tak stworzone handlery możemy łatwo uruchomić w dowolnym momencie – wystarczy posłużyć się metodą trigger:

1
jQuery(element).trigger('click');

To bardzo podstawowa obsługa zdarzeń znana przez wszystkich i stosowana na większości stron WWW. Niestety wielu programistów ogranicza się do tej podstawy, co jest grzechem w obliczu kolejnych możliwości.

Niestandardowe zdarzenia w JavaScript

Niestandardowe zdarzenia to bardzo przydatna funkcjonalność pozwalająca na lepszą organizację kodu, jak i usprawnienie sposobu reagowania na zdarzenia klienta.

Aby stworzyć niestandardowe zdarzenie wystarczy posłużyć się wyżej opisaną metodą on:

1
jQuery(element).on('myCustomEvent', doSomething);

by później je wywołać w dowolnym momencie:

1
jQuery(element).trigger('myCustomEvent');

Domyślnie, przeglądarki mają wbudowanych wiele różnych zdarzeń zależnych od wykonywanych operacji. I na tym też powinniśmy się wzorować, budując swoją aplikację.

Podejście to ma wiele zalet, jak choćby:

  • pozwala uniknąć zagnieżdżonych struktur kodu, lepiej organizuje kod,
  • możesz w dowolnym momencie wywoływać dowolne zdarzenia na dowolnych elementach (mówiąc inaczej, masz ogromną uniwersalność),
  • umożliwia tworzenie łatwych do rozszerzenia aplikacji przez możliwość dodawania wielu zdarzeń do pojedynczych elementów, jak również usuwanie czy nadpisywanie istniejących zdarzeń,
  • możesz tworzyć własne przestrzenie nazw w zdarzeniach (np. dla wtyczki), co pozwala wykonywać tylko zdarzenia z określonej przestrzeni,
  • nie musisz tworzyć osobnego, zewnętrznego API – Twoje zdarzenia będą globalne.

W praktyce/w oparciu o pseudokod, tak może wyglądać Twoja wtyczka:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var Tabs = function(element) {
    this.element = jQuery(element);
   
    // some code
};

Tabs.prototype.open = function() {
    // open tab
   
    this.element.trigger('open');
};

Tabs.prototype.close = function() {
    // close tab
   
    this.element.trigger('close');
};

jQuery.fn.tabs = function() {
    return this.each(function() {
        new Tabs(this);
    });
};

Tak przygotowaną wtyczkę łatwo użyć w praktyce:

1
jQuery(element).tabs();

Równie łatwo dobrać się później do dowolnego elementu, reagując na określone zdarzenia:

1
jQuery(elementA).on('open', doSomething);
Zdarzenia w przestrzeni nazw

Kolejną przydatną funkcjonalnością przy sterowaniu aplikacją przez zdarzenia są przestrzenie nazw. Pozwalają one uniknąć wykonywania niepotrzebnych operacji, jak również porządkują kod czy grupują podobne operacje.

Przestrzenie nazw w zdarzeniach przydają się choćby w elementach, do których podpinamy wiele różnych funkcji nasłuchujących zdarzenie. Kiedy już podepniemy nasze zdarzenie z przestrzenią, możemy je wykonać, nie wykonując innych funkcji przypisanych do tego zdarzenia (spoza naszej przestrzeni).

Posłużmy się przykładem. Mamy jakiś element i chcemy w momencie jego kliknięcia wykonać niezliczone, zasobożerne operację :-)

1
jQuery(element).on('click', doSomethingA);

W powyższym kodzie dodaliśmy funkcję nasłuchującą doSomethingA, która się wykona w momencie kliknięcia w element.

Do tego samego elementu postanowiliśmy dodać kolejną funkcję nasłuchującą zdarzenie click:

1
jQuery(element).on('click.myPlugin', doSomethingB);

Zwróć uwagę, że po nazwie zdarzenia dodaliśmy kropkę i następnie nazwę naszej przestrzeni. Od tego momentu, kliknięcie w element (lub odpalenie zdarzenia przez metodę trigger) spowoduje wykonanie dwóch funkcji, doSomethingA oraz doSomethingB.

Co jeśli nie będziemy chcieli wykonywać wszystkich funkcji nasłuchujących zdarzenie, by nie obciążać przeglądarki niepotrzebnymi zadaniami? Wystarczy poniższe polecenie:

1
jQuery(element).trigger('click.myPlugin');

Prosto i przyjemnie! Wywołując zdarzenie możemy również przekazać dodatkowe parametry:

1
jQuery(element).trigger('click.myPlugin', [param1, param2]);
Wady rozwiązania

Nie znam takowych :-)

Używam takiego podejścia od jakiegoś czasu i sprawdza się bardzo dobrze. Nie znaczy to jednak, że nie ma wad – jeśli ktoś zna, zapraszam do podzielenia się nimi w komentarzu.

Nie jest to jedyny sposób postępowania z funkcjami zwrotnymi w aplikacji. Być może w kolejnych wpisach omówię inne sposoby sterowania aplikacją (a jeszcze kilka fajnych się znajdzie).

Komentarze (6)

  1. Michał 25 kwietnia 2013

    Hmmm nie widzę nic złego w callbackach, ale w nadużywaniu funkcji anonimowych już tak.

    Skoro już tytułujesz tekst jako JS, to warto zaznaczyć co jest obsługiwane w JS, a co jest wymysłem jQuery (namespace).

    Dobrze by było dodać także informacje o preventDefault, stopPropagation i jak nimi kontrolować przepływ zdarzeń.
    Trochę po macoszemu potraktowałeś sposób tworzenia zdarzeń, a wiedząc co i jak można przesyłać [i]event driven[/i] pokazuje co potrafi.
    Nie tylko w JS :)

  2. eRIZ 25 kwietnia 2013

    Wszystko fajnie, tylko że w przypadku zdarzeń i podejścia do czegoś takiego, można łatwo sprawić, że kod jest nieczytelny i niewiadomo, co z czego wynika.

    Rozwiązaniem tego problemu jest coś, o czym nie mówi się w kursach, mianowicie Javascript Promises, w jQuery obecne jako Deferred.

  3. Kiro 25 kwietnia 2013

    Ale jaka jest różnica pomiędzy zwykłym wywołaniem funkcji (poza koniecznością użycia jQuery)? Może nie do końca zrozumiałem, ale nie widzę tutaj nic ponad dodatkową warstwę opakowującą funkcję :)

  4. Kamil Brenk 25 kwietnia 2013

    @Michał: Fakt, w tytule mogłem zastąpić JavaScript słowem jQuery. Ale z drugiej strony w czystym JavaScripcie też można napisać własny, prosty mechanizm obsługi niestandardowych zdarzeń, wraz z przestrzenią znaków i wszystkim innym, ale nie w tym był sens tego postu.

    Mógłbym też przy okazji omówić bąbelkowanie zdarzeń, obiekt jQuery.Event, stworzyć własny mechanizm niestandardowych zdarzeń i mnóstwo innych, powiązanych ze sobą rzeczy. Tylko, że:
    – długich wpisów nikt nie czyta, co najwyżej skanuje (widzę to po sobie),
    – długie wpisy się długo pisze i później jeszcze dłużej podchodzi do napisania kolejnego (braku czasu i motywacji na pisanie takich kolosów).

    Już jakiś czas temu postanowiłem zmienić nieco sposób prowadzenia bloga – mam wolną chwilę to siadam i piszę szybki artykuł omawiający jeden konkretny temat. Będzie kolejna chwila to omówię temat poboczny w osobnym wątku lub cokolwiek innego, czego chcę się nauczyć/utrwalić czy uznam, że warto się podzielić :-)

    @eRIZ: Deferred nie jest mi obce i też używałem w praktyce – dobrze się sprawdza i jest to temat na osobny wątek prezentujący alternatywne podejście (jedno z wielu w sumie).

    Cały JavaScript jest zdarzeniowy, więc tworzenie własnych zdarzeń jest jak najbardziej dobrym, naturalnym podejściem. Można sprawić, że kod będzie nieczytelny, jasne, ale to wszystko zależy od zastosowanej architektury (lub jej braku).

    Ostatnimi czasy uczestniczę w tworzeniu dość sporych serwisów z takim podejściem i nie ma większych problemów, aplikacja jest bardzo łatwa do rozbudowy/modyfikacji. Ba, takie podejście też wykorzystują popularne ostatnio frameworki, jak choćby Backbone (+ narzucają fajną architekturę do tego).

    @Kiro: patrz zalety, które można skontrastować z funkcyjnym podejściem.

  5. Michał 26 kwietnia 2013

    @Kamil – bez przesady, całego mechanizmu nie musisz opisywać. Jednak preventDefault i stopPropagation ma znaczny wpływ – szczególnie gdy podpinasz się pod zdarzenie a ktoś wcześniej je killował i dziwisz się że nie działa.

  6. Kamil Brenk 26 kwietnia 2013

    @Michał: to musiałbym też od razu dorzucić opis pointer-events, by nie było odwrotnej sytuacji, np. masz element (A) z podpiętym zdarzeniem i na nim pozycjonujesz inny element (B), bez żadnego zdarzenia. Mimo to kliknięcie w element B wywołuje zdarzenie z elementu A, a ktoś może sobie tego nie życzyć :-)

    We wpisie chciałem się skupić na jednym ze sposobów radzenia sobie z callbackami, zagnieżdżeniami i nieco lepszą organizacją kodu.



Kamil Brenk Blog

PHP, JavaScript, SQL, HTML

  • Informacje o blogu

    Kamil Brenk

    Blog o tworzeniu aplikacji na potrzeby sieci Web.

    Praktyczne przykłady, porady i sztuczki. PHP, SQL, AJAX, JavaScript, HTML i pochodne.

    Kanał RSS

    • Najnowsze
    • Komentarze
    • Popularne
    • Liczniki w CSS
    • Wyprzedaż książek o programowaniu!
    • Niestandardowy placeholder
    • JavaScript w modułach
    • Co dalej z blogiem?
    • Interaktywna mapa w HTML i CSS
    • Olsztyn: Jak wyseparować zawartość zassaną przez file_get_content?
    • ERMLAB: Od czegoś trzeba zacząć :) Wiele osób właśnie stawia na...
    • david: co nalezy wkleić na stronę aby plik ze stylami był ladowany...
    • krynicz: Nie jestem pewien czy dobrze to rozumiem: wpisujemy OG w...
    • yaro: Jak zmienić re_write znak "_" na "-"?
    • Piotr: stworzyłem prostą stronkę w PHP, czy jest możliwość aby...
    • MichalR: Super sprawa... bardzo przydatne.. dzieki i pozdrawiam..
    • Niestandardowe czcionki na stronie
    • Sposoby wczytywania JavaScript
    • Gramatyka w PHP, część 1
    • Umowa i zaliczka dla freelancera
    • Wysyłanie wiadomości SMS w PHP
    • Projekt aplikacji po stronie klienta
    • Własny mechanizm Feed
  • Szukajka
    Wpisz co chcesz wyszukać na stronie…
  • Kategorie
    • Apache
    • Freelancer
    • Front-end Development
    • HTML5 & CSS3
    • Inne
    • JavaScript
    • Książki
    • PHP
    • Po godzinach
    • Pozycjonowanie
    • Protokół HTTP
    • SQL
    • Wyrażenia regularne
  • Moje serwisy
    • Testy zawodowe
    • Miłość, uczucia i seks
  • Czytane blogi
    • Wojciech Sznapka
    • Wojciech Soczyński
    • Michał Wachowski
    • Tomasz Kowalczyk
    • Filip Górczyński
  • Strona główna
  • Curriculum Vitae
  • O mnie
  • Przykład: Gramatyka w PHP
  • Przykład: Kompresja CSS
  • Przykład: Kompresja JavaScript
  • Przykład: Skracanie linków
  • Przykład: Wykrywanie serwera HTTP
  • Przykład: Własna bramka SMS
  • Mapa strony
  • Kontakt

Kamil Brenk © 2010. All rights reserved.

Designed by FTL Wordpress Themes brought to you by Smashing Magazine.

Do góry ∧