Perfection or Vanity

Project: Terminated

Blog nie jest już dalej prowadzony ani aktualizowany. Mimo tego, wpisy i komentarze są dalej dostępne. Możesz przeczytać pożegnalny wpis albo przejść do archiwum.

Można śmiało powiedzieć, że cała magia JavaScriptu opiera się na zdarzeniach i akcjach wykonywanych po odebraniu któregoś z nich. Zdarzenia to na przykład wpisanie tekstu do formularza, kliknięcie w link, umieszczenie kursora nad obrazkiem, załadowanie dokumentu i tym podobne. Bez nich praktycznie nie istniałyby skrypty.

Aby obsłużyć jakieś zdarzenie potrzeba je przechwycić i wykonać odpowiedni kod. Netscape wprowadził do HTML-a nowe atrybuty obsługujące zdarzenia takie jak onclick, onmouseover, onsubmit — Microsoft podążył tym szlakiem i w końcu okazało się, że jest ich po prostu masa.

Mało kto zdaje sobie sprawę, że to rozwiązanie jest wręcz starożytne.

Pisząc stronę podchodzimy do niej z trzech stron:

  1. Tworzymy zawartość, wkładamy ją w odpowiednią strukturę - HTML
  2. Określamy sposób prezentacji treści - CSS
  3. Dodajemy warstwę zachowania (np.: podczas interakcji z użytkownikiem) - JavaScript

Umieszczając link wraz z atrybutem onclick robimy tak samo źle, jak umieszczając style liniowe poprzez style="". Jeśli uważasz, że można zrobić w niektórych miejscach wyjątek i określić prezentację w strukturze dokumentu, możliwe że dojdziesz do tych samych wniosków jeśli chodzi o JavaScript. Czasem wszakże potrzebujemy jeden mały onclick w jakimś zapomnianym miejscu strony. Moim zdaniem jednak warto podejść do zagadnienia nowocześnie i odrzucić on…… raz na zawsze. 1

Nigdy więcej onclick

Jak więc dodawać zdarzenia do dokumentu i je przechwytywać? Na początek musimy odnaleźć element bądź elementy, którymi należy się zająć. Widnieje tutaj jeszcze jedna zaleta nieinwazyjnego JavaScriptu - jeśli mamy sto elementów ze stoma onmouseover i onmouseout, to prowadzi to do kłopotów:

  • HTML rośnie znacznie - sto razy kilkadziesiąt bajtów to kilka kilobajtów więcej w każdym dokumencie.
  • Zmiana kodu JavaScript wymaga używania funkcji Znajdź i Zamień, czasem z wyrażeniami regularnymi a czasem jest to po prostu niemożliwe i trzeba edytować wszystko ręcznie.

Wystarczy jednak użyć mechanizmów wyszukiwania elementów w drzewie dokumentu aby pozyskać ich uchwyty albo całe ich tablice i przyporządkować do nich zdarzenie.

Funkcja addEvent

W idealnym świecie używalibyśmy metody DOM addEventListener, lecz Explorer honoruje tylko swoją metodę nazywaną attachEvent. Powstało bardzo dużo uniwersalnych funkcji, a swego czasu Peter Paul Koch urządził konkurs na najlepszą implementację funkcji addEvent. Wygrała taka:

  1. function addEvent(obj, type, fn) {
  2. if (obj.addEventListener) {
  3. obj.addEventListener(type, fn, false);
  4. } else if (obj.attachEvent) {
  5. obj["e"+type+fn] = fn;
  6. obj[type+fn] = function() {obj["e"+type+fn](window.event); }
  7. obj.attachEvent("on"+type, obj[type+fn]);
  8. }
  9. }

Przyporządkowanie obsługi kliknięcia (onclick) następuje w ten sposób:

  1. addEvent(element, "click", eventHandler)

W wywołaniu pomijamy prefiks "on", a jako trzeci argument przekazujemy nazwę funkcji (eventHandler), która ma być wywołana po kliknęciu w element.

Funkcja obsługująca ten klik powinna wyglądać mniej więcej tak:

  1. function eventHandler(e) {
  2. // instrukcje
  3. (e.preventDefault) ? e.preventDefault() : (e.returnValue = false);
  4. }

Ostatnia linijka z powodzeniem wykonuje robotę za wszelkie return false; jakie dostawialiśmy na końcu onclick w linkach. Po prostu anuluje domyślą akcję, liczy się tylko to co znajduje się w środku funkcji eventHandler.

W co kliknęliśmy?

Co jednak, gdy chcemy wykonać operacje na elemencie z którego wywołujemy zdarzenie, albo chcemy pobrać jakieś jego właściwości? Zwykle robiło się to przez przekazanie zmiennej this. Tutaj będzie trzeba znaleźć element, którego zdarzenie obsługujemy w trochę bardziej skomplikowany sposób.

Funkcja addEvent umieszczona powyżej pozwala odwoływać się przez this, akapity poniżej są rozwiązaniem dla innych implementacji dodawania zdarzeń.

Jak już pisałem, Explorer ma swoje sposoby wykonywania różnych czynności. Zmienna e, używana w funkcji eventHandler zawiera informacje o tym zdarzeniu, a z nich możemy przez e.target wyciągnąć kliknięty element. Lecz nie dla IE. ;-)

  1. function eventHandler(e) {
  2. var el;
  3. if (window.event && window.event.srcElement) { el = window.event.srcElement; }
  4. if (e && e.target) { el = e.target; }
  5. if (!el) { return; }
  6. (e.preventDefault) ? e.preventDefault() : (e.returnValue = false);
  7. }

Zmienna el przechowuje to samo co kiedyś przekazywane this. Przykład użycia.

Przekazywanie zmiennych

Czy da się jednak przekazać funkcji obsługującej zdarzenie jakąś zmienną? W konstrukcji, którą napisałem wyżej nie jest to możliwe, jednak istnieją proste, a zarazem kompleksowe rozwiązania. Można skorzystać z klasy Event biblioteki Prototype albo Yahoo! UI Library.

  • 1) Wiem, sam muszę w HTML-u bloga wyrzucić obsługę zdarzeń z paru miejsc. ;)

Najmocniej przepraszam za tak rzadkie posty, strasznie dużo mam obecnie na głowie, mam też nadzieję że we wrześniu powrócę do regularnego pisania. :-(

Informacje i hiperłącza

Blog o projektowaniu zgodnych ze standardami stron internetowych.

Praktyczne przykłady, sztuczki CSS, sposoby obchodzenia błędów przeglądarek, lekki i nieinwazyjny JavaScript, użyteczny design, dostępność i skrypty użytkownika.

RSS

Informacje o wpisie

Napisał riddle 27 sierpnia 2006 o 22:26

Kategorie: Internet Explorer, JavaScript & DOM, Standardy sieciowe

Dodaj do:

Wpisy archiwalne

Archiwum miesięczne

Dzięki!

Dodaj bloga do Technorati Favorites Dodaj bloga do Del.icio.us Blog należy do sieci 10przykazań.com

  1. Hmm, rozumiem plusy takiego rozwiązania przy serwisie, który pozwala na pełną funkcjonalność dla użytkowników bez JS - zaś sam java script tylko dodaje jakieś ułatwienia.

    Ale co gdy np. działanie podstrony jest całkowicie uzależnione od JavaScriptu? Np. w przypadku map google + np. tagi itp. sprawy?

    Czy jest jakaś korzyść płynąca ze stosowania addEvent() ?

  2. Hmm, ubiegłeś mnie, bo o tym będę pisał w drugiej części wyjaśniając czemu należy zawsze myśleć o „użytkownikach bez JS” i nigdy nie umieszczać rzeczy uzależnionych od JS (wyjątkiem są aplikacje WWW). :-)

  3. Ładnie Riddle, ładnie. Tak to jest jak sie pisze o czymś o czym sie nie ma pojęcia.

    Wróć teraz do podlinkowanego przez Ciebie wpisu na quirksmode i znajdź *czemu* PPK rozpisał ten konkurs.

    Podpowiedź:
    sprawdź czy 'this' działa z kodem Johna Resiga i wyedytuj notkę.

  4. Huh, masz rację! Musiałem czytając to przeoczyć, damn me. :-/

  5. Hmm... Co prawda na ten konkurs na blogu PPK widziałem już dawno temu, rozwiązanie Johna Resiga podobnie jak PPK uznałem za najlepsze... Ale tak sobie dziś pomyślałem czy nie możnaby zgrabniej...

    function addEvent(o,t,f) {
    if(o.addEventListener)
    o.addEventListener(t,f,!1)
    else if(o.attachEvent)
    o.attachEvent("on"+t,
    f.call?function(){f.call(o,window.event)}:
    function(){o[f]=f;o[f](window.event)}
    )
    }

    Testowałem na IE6, IE5.5 i IE5 tyle że to IEs4Linux (na wine). Proszę o testy. Jutro postaram się sprawdzic na windowsie.

  6. Moze mi ktos wytlumaczyc po co jest obj["e"+type+fn]??
    Nie mozna by po prostu dac obj["e"+type]? O ile sie nie myle fn to funkcja, jak wiec funkcja moze byc stringiem nazywajacym pole obiektu?

  7. @Thinker:
    'Moze mi ktos wytlumaczyc po co jest obj["e"+type+fn]?? '

    Z grubsza chodzi o to by z funkcji zrobić metodę obiektu pod które podpinamy zdarzenie. Wtedy this jest referencją do tego obiektu. Oczywiście zabieg ten musimy przeprowadzić tylko dla IE.

    'Nie mozna by po prostu dac obj["e"+type]?'

    Nie, bo w ten sposób dodając każdą kolejną funkcję do obsługi tego samego zdarzenia dla danego obiektu napisywałbyć poprzednie (nadpisując metodę 'e'+type). Czyli byłby to taki setEvent a nie addEvent :)

    'O ile sie nie myle fn to funkcja, jak wiec funkcja moze byc stringiem nazywajacym pole obiektu?'

    Tak. W JS funkcja jest także obiektem a wspomniany zabieg wywołuje niejawne wywołanie metody toString() zwracające w tym przypadku ciąg znaków będący "ciałem" funkcji.

  8. eh.. http://www.dustindiaz.com/screencast-episode-01/ - Tak to robi Dustin - Yahoo! Web Developer :)

  9. Eluś, mógłbyś Twoje rozwiązanie na pl.comp.lang.javascript wrzucić?

  10. @PablO:
    Jasne, muszę tylko sobie wcześniej skombinować jakiś czytnik i go skonfigurować :) Dawno już nie zaglądałem na grupy, byłem troche zajęty.
    Do wieczora postaram się tam sprawę opisać. Zrobi się testy, obada sprawę... Może ktoś rzuci nowe światło na problem, udoskonali nieco kod.

  11. @Eluś:
    Nie dziś to jutro, tylko nie zapomnij plz, bo może być ciekawie. Ja (JS newbie ;)) np. poszedłem inną drogą i skorzystałem z closures:
    function aEL(o,t,f,p,b){return(o.addEventListener)?o.addEventListener(t,CF(f,o,p),b||false):(o.attachEvent)?o.attachEvent('on'+t,CF(f,o,p)):!1
    function CF(f,o,p){return function(e){return false===f(e||window.event,o,p)?e.preventDefault?e.preventDefault():!1:1}}}
    I przy okazji załatwiłem jeszcze kilka spraw:
    - return false; działa jak przy normalnej obsłudze zdarzeń
    - w wywoływanej funkcji mamy dostępne kolejno: zdarzenie (e), obiekt do którego obsługa zdarzenia jest przypięta (o) i dodatkowe parametry, które ktoś chciałby do tej funkcji przekazać (p)
    Ciekaw jestem co zepsułem ;]

    @Riddle:
    Co chwilę mi wyskakuje coś takiego:
    Błąd: urchinTracker is not defined
    Plik źródłowy: http://perfectionorvanity.com/files/core.js
    Lina: 74

  12. @PablO
    ok 2h przed tym jak umieściłes ten komentarz otworzyłem temat na p.c.l.js. Przenieśmy się może tam z dyskusją zamiast tu śmiecić :)
    A kod ciekawy. Podoba mi się return false

  13. Nigdzie nie śmiecicie, nauczę się czegoś nowego dzięki Wam. ;) A ta funkcja umożliwająca przekazywanie zmiennej jest supersmocza. :)

  14. Spadłeś mi z nieba tym wpisem! Właśnie pisze cmsa i czegoś takiego potrzebowałem :)

  15. @Eluś:
    Heh, faktycznie, nie wiem dlaczego Hammster mi nie chciał ściągnąć nowych postów w nocy :/

  16. Mam jedno pytanie niezwiązane z postem. Gdzie się tego wszystkiego nauczyles o xhtml/css/js/dhtml? Studia czy na 'wlasna rękę' via internet/ksiązki z helionu?

  17. http://bennolan.com/behaviour/

  18. Ponieważ na pl.comp.lang.javascript cisza, pozwalam sobie tutaj wkleić ostateczną wersję ;]

    /* uwaga na złamane wiersze */
    function aEL(o,t,f){var a=[]
    for(i=3;i<arguments.length;)a[i-3]=arguments[i++]
    o[t+f]=function(e){return !1===f.apply(o,[e||window.event].concat(a))?e.preventDefault?e.preventDefault():!1:1}
    return(o.x=o.addEventListener)?o.x(t,o[t+f],!1):(o.x=o.attachEvent)?o.x('on'+t,o[t+f]):!1
    }

    function rEL(o,t,f){f=o[t+f]
    return(o.x=o.removeEventListener)?o.x(t,f,!1):(o.x=o.detachEvent)?o.x('on'+t,f):!1
    }

    *Nie działa w IE5.0*

    Wywołanie:
    aEL(obiekt, typZdarzenia, funkcja [, parametr]*)
    funkcja zostanie wywołana z parametrami: zdarzenie [, parametr]*
    W wywołanej funkcji this jest referencją do obiektu, do którego jest przyczepione zdarzenie.
    Wykonanie w funkcji return false; działa jak w zwykłym modelu zdarzeń.

    Przykład:
    aEL(window, 'load', tada, 'załadowała się', 'cała', 'strona')
    function tada(ev, a, b, c, d) {
    alert(this + ' - ' + ev + ': ' + a + ' ' + b + ' ' + c)
    }

    rEL(window, 'load', tada)

  19. Zastanawiało mnie czemu w fazie produkcji / nauki ludzie używają pojedynczych zmiennych. :]

    A kod przydatny, dzięki. :]

    Sztywny: Znam Behaviour, ale heh… zależy co gdzie musisz dodać, nie opłaca się używać całej biblioteki do zwyczajnego bloga - natomiast jeśli chodzi o jakieś duże sajty to owszem, opłaca.

    Myggan: Z netu, z kodów źródłowych, część na własną rękę. To nie jest trudne, a do tego bardzo to lubię. ;-)

  20. Ja dodam ze Behaviour ma braki w funkcjonalnosci, min nie przekazuje obiektu z event'em do metody podpinanej pod zdarzenie. Mi udalo sie napisac YUI! Behaviour oparty o Yahoo UI! i klase cssQuery. Dzieki temu i kilku specjalnym zdarzeniom ('init'), moge korzystac z pseudoselektorow css 3 i zdarzen podpinanych do elementow z jeszcze wieksza elastycznoscia ;)

  21. @Riddle:
    Bo mniej bajtów = lżejszy kod, a w przypadku JS to ma znaczenie. Takie zboczenie to jest :P

    Jeszcze update mały dzięki pomocy Elusia:
    function aEL(o,t,f){var a=Array.prototype.slice.call(arguments,3)
    o[t+f]=function(e){return !1===f.apply(o,[e||window.event].concat(a))?e.preventDefault?e.preventDefault():!1:1}
    return(o.x=o.addEventListener)?o.x(t,o[t+f],!1):(o.x=o.attachEvent)?o.x('on'+t,o[t+f]):!1}

  22. A jak dodac zdarzenie z argumentami ?

  23. A mnie to jakoś nie działa (te przykłady by PablO). Wszystko mam w osobnym pliku *.js (link: http://pastebin.de/13256) i niestety nie działa nic się nie wyświetla ani Firefox nie zgłasza błędów JavaScript :/

  24. jesli chcecie powstrzymac przed wykonaniem inne funkcje przypisane do danego zdarzenia polecam poczytac o metodzie e.stopPropagation();
    a dla tych przegladarek ktore nie wiedza co to W3C trzeba robic
    e.cancelBubble = true;

  25. (Komentarz zmodyfikowany 06.12.2006 o 19:10)

    OT: Co z Twoim GoneDarkiem?

  26. Mój “schiz” - semantyka - IMHO

    Któregoś razu trafiłem na blog Riddle&#8217;a. Koleś porusza nawet niezłe tematy dotyczące projektowania www, są to głównie takie, które poruszają ich budowę, to, co jest pod maską. Parę miesięcy temu liznąłem trochę XHTML-a i CSS-[...]

  27. a ja pozostanę sceptyczny do tego rozwiązania. ono nie zawsze jest najefektywniejsze. Załóżmy, że w <body onload=”...”> mam funkcję inicjacyjną, którą ładuję zewnętrznym plikiem javascriptowym. plik ten jest w normalnych warunkach ładnie keszowany, o ile nie jest zmieniany za każdym razem. Jeśli wewnątrz mam tabelkę, która nie jest tak na 100% stała dla każdego wywołania (np zmieniają się id wiersza tabelki), to inicjacę zdarzenia kliknięcia na wierszu muszę wprowadzić w plik zewnętrzny zmuszając też przeglądarkę do każdorazowego przesłania go przez sieć na nowo (a ten plik nie musi być mały). wydaje mi się, że czasami lepiej załatwić sprawę kilkoma onclick=„funkcja(argument)” niż babrać się udziwnienia.

  28. Opera mini wykonuje podpięty skrypt, a pomimo to po krótkim czasie wyświetla komunikat: While loading http://google.pl – request timed out while loading http://google.pl :(

  29. Witam
    Mam pytanie mam ten przyklad zrobiony przez PablO w sosobnym pliku i nic sie nie dzieje ani błedu ani wywołania;/

    Prosiłbym o jakąś pomoc bo sama idea wywolywania js bez ingerencji w kod bardzo mi sie spodobała:)

    Ten kod co jest w artykule co wywyoluje sama funkcje dziala bez zarzutu. A ten drugi nie wiem jak go uryść bo to jeszcze dla mnie za wysoka szkola jazdy;/ i nie wiem czemu nie trybi:((

    I jeszcze jedno pytanie:
    Czy w tej funkcji z artykułu: function eventHandler(e) {...}
    mozna umieszczac wywołania funkcji napisanych wczesniej czy tylko sam kod w niej umieszczac bo sam kod mi dziala a wywołanie funkcji nie;/

    Nadmieniem ze sie dopiero ucze wiec bądcie wyrozumiali:)

  30. Tak się zastanawiam: po co pisać o dodawaniu zdarzeń w czasach gdy mamy JQuery? No chyba, że wprowadzimy podzial na JS „nisko-” i „wysokopoziomowy”. ;-)
    Chociaż z drugiej strony – jeśli potrzebujemy niewiele JS i zależy nam na oszczędzaniu transferu, nie ma sensu podłączać kilkudziesięciu kb JQuery po to by obłużyć onclick na jednym elemencie…
    Ale przy większej ilości zdarzeń i elementów do oprogramowania, to trochę utrudnia życie, zwłaszcza jeśli klient co chwilę życzy sobie innych zmian w projekcie…

  31. Dla mnie JavaScript to ciemna magia.
    Obecnie mam problem w prawidłowym skonstruowaniu kodu na mojej stronce
    http://www.versus-odszkodowania.pl/test/
    tak, żeby przechodziła walidację (jest w xhtml’).
    Wiem, że nie można używać embed itp, ale nie jestem do końca przekonany, czy użycie alternatywnych kodów, będzie lepsze (samo object, etc)? Co o tym sądzicie?

Dodaj komentarz

Do formatowania komentarzy używaj Textile (HTML nie działa). Szczególnie jeśli wklejasz większe fragmenty kodu. W razie niepewności użyj podglądu komentarza.

Wypowiedzi obraźliwe, infantylne oraz nie na temat będą moderowane – pisząc postaraj się zwiększyć wartość dyskusji.

Komentarze nie służą do wysyłania wiadomości albo informowania o błędach, itd. Chcesz coś mi napisać – skontaktuj się.