Strona początkowa

Zdarzenia

Zdarzenia to czynności, które użytkownik wykonuje podczas odwiedzania naszej strony. Przykładowymi zdarzeniami mogą być np. przesunięcie kursora na obrazek, kliknięcie jakiegoś linka, wysłanie formularza, zaznaczenie jakiegoś obiektu, naciśnięcie klawisza itp.

Można zaryzykować stwierdzenie, że żaden większy skrypt nie może obejść się bez zdarzeń. To właśnie one kreują tą pożądaną przez nas interaktywność...

Większość zdarzeń wywoływana jest przez użytkownika. Użytkownik kliknie to, użytkownik zrobi tamto itp. Istnieją też zdarzenia, które nie są bezpośrednio spowodowane przez użytkownika - np. zdarzenie load, które zachodzi np. gdy załaduje się strona.


Javascript udostępnia kilkanaście typów zdarzeń, dzięki którym jesteśmy panować nad tym, co się dzieje na stronie:

Zdarzenie:Opis:
onAbortWystępuje, gdy zaniechano ładowania strony
onBlurwystępuje, gdy obiekt przestał być aktywny
onDblClickwystępuje, gdy podwójnie klikniemy na obiekt
onChangewystępuje, gdy obiekt zmienił swoją zawartość
onClickwystępuje, gdy obiekt został kliknięty
onErrorwystępuje, gdy wystąpił błąd w skrypcie
onFocuswystępuje, gdy obiekt stał się aktywny (został wybrany)
onKeyDownwystępuje naciśnięcia klawisza na klawiaturze
onKeyUpwyetępuje gdy puścimy klawisz na klawiaturze
onLoadwystępuje, gdy obiekt został załadowany
onMouseOverwystępuje, gdy kursor znalazł się na obiekcie
onMouseOutwystępuje, gdy kursor opuścił obiekt
onSelectwystępuje, gdy zawartość obiektu została zaznaczona
onSubmitwystępuje, gdy formularz został wysłany
onUnloadwystępuje, gdy zmieniono wyświetlaną stronę

Aby zdarzenie było dostępne dla danego obiektu, musimy je dla niego zarejestrować. Istnieje kikla metod na rejestrację zdarzenia dla obiektu.

Rejestrowanie zdarzenia inline

Metoda inline przypisywania zdarzeń polega na określeniu zdarzenia wewnątrz znacznika. Na przykład:

	<a href="jakasStrona.html" onclick="alert(' Kliknąłeś link! ')"> kliknij </a>
	

Od tej chwili po kliknięciu na link zostanie wywołane zdarzenie click, które wyświetli okienko dialogowe Alert.

Ten model rejestrowania zdarzeń jest bardzo zły. Po pierwsze miesza nasze skrypty z treścią html (co jest dużym błędem), po drugie nie przekazuje automatycznie do funkcji obiektu, który funkcję wywołał (patrz podrozdział this). Możemy to obejść przekazując do funkcji element jako argument np. wypisz(this), jednak wciąż nie jest to dobre rozwiązanie.

Jako super bohaterowie będziemy korzystać tylko z dobrych metod, dlatego pozostawiamy ten sposób w domu starców i przechodzimy do ciekawszych sposobów obsługi zdarzeń.

Oddzielenie javascript od html - tradycyjny model rejestrowania zdarzenia

Poniższa metoda pozwala oddzielić kod Javascriptu od kodu HTML. Dzięki temu kod strony staje się przejrzysty i bardziej elastyczny niż w przypadku stosowania rejestrowania zdarzeń wewnątrz znaczników. Ta metoda ma kilka nie zaprzeczalnych plusów - pozwala trzymać kod JS w oddzielnych plikach, oraz pozwala na "automatyzację" w przypisywaniu zdarzeń - np za pomocą wykonania pętli po elementach na stronie.

Ogólna konstrukcja przypisania zdarzenia ma postać:

	element.onclick = zrobCos
	element2.onmouseover = zrobCosInnego
	

Element i element2 to elementy, dla których przypisujemy zdarznia onclick i nomouseover, funckcje zrobCos i zrobCosInnego będą wywoływane, gdy zajdą te zdarzenia.
Zauważyłeś, że przy podpinaniu funkcji do zdarzeń pomijamy nawiasy? Dzieje się tak dlatego, ponieważ nie chcemy odpalać funkcji, a tylko ją podpiąć pod dane zdarzenie.

Wszystkie zdarzenia w Javascripcie są funkcjami. Aby zasymulować np. zdarzenie onclick skorzystamy z instrukcji:

	element.onclick()
	

Powyższa instrukcja spowoduje zasymulowanie kliknięcia na naszym elemencie, co jest równoznaczne z wykonaniem instrukcji które wcześniej przypisaliśmy temu zdarzeniu.

Jeżeli chcesz usunąć rejestrowanie zdarzenia dla danego obiektu, wystarczy, że przypiszesz mu wartosć null:

	element.onclick = null
	

Zwróć też uwagę, że nazwy zdarzeń podajemy z małych liter, tak więc instrukcja element.onClick będzie błędna.

Przykładowo przypiszmy guzikowi zdarzenie onclick:

	<input type="button" id="guzik" value="kliknij" />
	
	<script type="text/javascript">
	function wypisz() {
		alert(' zostałem kliknięty! ');
	}

	document.getElementById('guzik').onclick = wypisz
	</script>
	

Oczywiście jeżeli takie zdarzenie chcielibyśmy przypisać powiedzmy do naszych 1000 nieszczęsnych linków, wystarczyło by, byśmy wykonali odpowiednią pętlę:

	var a = document.getElementsByTagName('a');
	for (x=0; x<a.length; x++) {
		a[x].onclick = function() {...}
	}
	

W powyższym przykładzie przypisaliśmy do zdarzenia anonimową funkcję (czyli taką, którą tworzymy przy deklaracji zdarzenia). Funkcja taka może odpalać kilka funkcji naraz oraz może pobierać parametry (co przy deklaracji bez nawiasów jest niemożliwe).

	function wypisz(tekst) {
		alert(tekst);
	}

	document.getElementById('guzik').onclick = function() {
		wypisz('Przykladowy tekst')
	}
	

Kolejny przykład pokazuje jak możemy przypisywać zdarzenia do wielu obiektów. Wystarczy zastosować pętlę:

	var akapity = document.getElementsByTagName('p');
	for (i=0; i<akapity.length; i++) {
		akapity[i].onclick = function () {
			this.style.color = 'red';
			this.style.backgroundColor = 'yellow';
		}
	}
	

Od tej pory gdy klikniemy na którykolwiek akapit na stronie, zmienimy jego kolor na czerwony z ciemnym tłem.

Nowy model rejestracji zdarzeń

Problem z tradycyjnym modelem polega na tym, że do jednego elementu możemy podpiąć tylko jedną funkcję dla jednego rodzaju zdarzenia. Rejestrując nową funkcję do danego zdarzenia nadpisujemy starą. Możemy to oczywiście obejść (wspólna funkcja odpalająca), jednak jest to mało logiczne i przysparza kłopotów z późniejszym odrejestrowaniem takich zdarzeń.

Problemów takich nie mamy korzystając z "zaawansowanego" modelu rejestrowania zdarzeń opierającego się na metodzie addEventListener().

Przyjmuje ona 3 argumenty: typ zdarzenia, funkcja do wywołania, oraz trzeci (true/false) który włącza lub wyłącza bąbelkowe zachowanie zdarzeń. W praktyce trzeci argument zawsze pozostaje jako false, więc nie musimy sobie nim zawracać głowy.
Kto programował choć przez chwilę w Action Script 3 - nie będzie miał żadnych problemów ze zrozumieniem tego typu obsługi zdarzań.

Zarejestrowanie nowego zdarzenia ma postać:

	//rejestrujemy 3 zdarzenia click dla elementu
    element.addEventListener('click', startDragDrop, false)

    element.addEventListener('click', wypiszCos, false)

	element.addEventListener('click',function () {
		this.style.color = 'red';
	},false);
	

Od tej pory po pojedynczym kliknięciu zostaną wywołane wszystkie trzy funkcje.

Jako funkcja możemy podać nazwę funkcji do wywołania, lub stworzyć funkcję anonimową. Zalecam korzystanie z oddzielnych funkcji, gdyż wtedy łatwiej będziemy mogli takie pojedyncze funkcje odrejestrować.

Do odrejestrowania funkcji z danego zdarzenia służy metoda .removeEventListener(), która przyjmuje dokładnie takie same argumenty jakie były użyte do zarejestrowania danego zdarzenie za pomocą addEventListener. Aby więc odrejestrować nasze powyższe funkcje, skorzsytamy z kodu:

	element.removeEventListener('click', startDragDrop, false)
    element.removeEventListener('click', wypiszCos, false)
	

Niestety nasza ostatnia funkcja jest funkcją anonimową - nie ma nazwy i nie jest podstawiona pod żadną zmienną. Nie jesteśmy więc w stanie przekazać dla removeEventListener drugiego parametru, tym samym nie jesteśmy w stanie odrejestrować tej funkcji!. Mamy natomiast 2 rozwiązania. Odrejestrować ją podczas rejestrowania (wykorzystując arguments.callee):

        element.addEventListener('click',function (e) {
		    this.style.color = 'red';
            this.removeEventListener('click', arguments.callee, false);
	    },false);
    

...lub jej w ogóle nie odrejestrowywać. Znacznie częściej będziemy rejestrować zdarzenia niż je odrejestrowywać, więc nie zawsze brak możliwości odrejestrowania będzie problemem.

Funkcje anonimowe przydają się w przypadkach, gdy chcemy rejestrować zdarzenia z przekazanymi parametrami:

    element.addEventListener('click',
        function() {
            wypiszCos('Tutaj jakis tekst do przekazania')
        },
    false)
    

Główny problem z tym modelem rejestracji polega na tym, że IE stosuje swój model. Aby więc zarejestrować zdarzenie dla IE, skorzystamy z metody element.attachEvent('onclick',doSomething). Pierwszy atrybut to nazwa zdarzenia, poprzedzona przedrostkiem "on". Drugi to nazwa funkcji, która zostanie wywołana. Model IE nie przewiduje wyłączania bąbelkowego zachowania się zdarzeń.

Aby odrejestrować zdarzenie dla IE, użyjemy metody element.detachEvent('onclick',spyOnUser)

Ujarzmiamy wszystkie modele

Z pewnością nie za bardzo będzie nam pasowało co chwile sprawdzanie, czy mamy do czynienia z IE, czy z normalnymi przeglądarkami. Dlatego napiszmy funkcję dodającą zaawansowaną rejestrację zdarzeń dla wszystkich modeli:

	function addEvent(o, zdarzenie, funkcja) {
		if (o.addEventListener) {
			o.addEventListener(zdarzenie, funkcja, false);
		} else if (o.attachEvent) {
			o.attachEvent("on"+zdarzenie, funkcja);
		} else {
			eval(o+".on"+zdarzenie+"="+funkcja+";");
		}
	}
	

Działanie funkcji jest bardzo proste. Jeżeli przeglądarka obsługuje addEventListener, wtedy korzystamy z tego modelu. Jeżeli korzysta z attachEvent, korzystamy z tego modelu, dodając do nazwy zdarzenia przedrostek "on". Jeżeli oba modele nie zostały wykryte, wówczas wykonujemy funkcję eval, do której przekazujemy ciąg tworzący kod rejestrujące zdarzenie w modelem tradycyjnym - np akapit.onclick = wypisz;

Od tej chwili możemy rejestrować zdarzenia dla elementów w następujący sposób:

	function init() {
		addEvent(button1,"click",wylacz);		
		addEvent(button2,"click",wylacz);		
	}

	addEvent(window,"load",init);
	

Polecam obejrzeć filmik na stronie http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/, w którym bardzo fajnie jest omówiona nieco inna metoda obsługi wszystkich przeglądarek.

Który model wybrać?

Na pewno nie dziadka inline. Może na siłę udało by mi się wymyśleć jakiś szczególny przykład, kiedy musiałbyś zastosować ten model (przykład, który byłby przeznaczony dla leniwców). Osobiście zachęcam do stosowania nowoczesnego ("zaawansowanego" jak ja to nazywam) sposobu obsługi zdarzeń. Pamiętajmy o tym, że im nowsze przeglądarki, tym mamy większe możliwości - także w przypadku panowania nad zdarzeniami.

Jak zauważyliście, w przykładach w kurie wciąż stosuję model tradycyjny.
Spowodowane jest to tym, że spora część zamieszczonych tutaj tekstów była pisana w czasach, gdy większość ludzi nie wiedziała co oznacza słowo "standard".
Jak też widać z powyższych przykładów, do póki na rynku będą istniały stare przeglądarki IE, doputy trzeba będzie posiłkować się funkcją tłumaczącą tym przeglądarkom, że są w błędzie.
Stąd też wciąż często stosuję sposób tradycyjny... Co nie zmienia faktu, że z łatwością można taki kod przerobić na barzdiej nowoczesny.

Wykorzystanie słowa kluczowego this

Javascript udostępnia słowo kluczowe this, które wskazuje na obiekt, który wywołał daną funkcję. Słowo to jest bardzo użyteczne w przypadku rejestrowania zdarzeń dla obiektów.
Przykładowo chcielibyśmy zmienić kolor czcionki dowolnego obiektu, do którego przypiszemy zdarzenie onmouseover:

	function zmienKolor() {
		this.style.color = '#CC0000';
	}

	element.onmouseover = zmienKolor;
	inny_element.onmouseover = zmienKolor;
	

Lub to samo z parametrami:

	function zmienKolor(jaki) {
			this.style.color = jaki;
	}

	element.onmouseover = function() {
		zmienKolor('#CCFF33');
	}
	inny_element.onmouseover = function() {
		zmienKolor('#FF9966')
	};
	

Wkraczamy w głąb zdarzenia

Javascript udostępnia nam specjalne właściwości, dzięki którym możemy bardziej dokładnie badać każde zarejestrowane zdarzenie.

Aby odczytać właściwości zdarzenia musimy posłużyć się pseudo parametrem, który będziemy przekazywać do deklarowanej funkcji (w naszych przykładach taki parametr nazwiemy e).

 
	element.onclick = function(e) {
		//...dzięki pseudoparametrowi e mamy dostęp do właściwości zdarzenia
	}
	

Niestety IE nieco inaczej podchodzi do tego. Aby odczytać właściwości zdarzenia w tej przeglądarce, musimy skorzystać z obiektu window.event. Nie jest to jednak problem. Wystarczy w zadeklarowanej funkcji sprawdzić, czy pseudo parametr e został przekazany, a jeżeli nie istnieje podstawić pod niego window.event. Załatwiamy to jedną linijką:

	if (!e) var e = window.event;
	

Typ zdarzenia

Jakiego typu jest dane zdarzenie? Aby się tego dowiedzieć, wystarczy odczytać właściwość type każdego zdarzenia:

	function sprawdzTyp(e) {
		if (!e) var e = window.event;
		return e.type;
	}

	//...
	
	document.getElementById('Jakis_guzik_na_stronie').onclick = function(e) {
		alert('Typ zdarzenia: ' + sprawdzTyp(e));
	}
	

Który klawisz został naciśnięty

Wartość naciśniętego klawisza jest przechowywana w właściwości keyCode zdarzenia. Wystarczy ją odczytać i przesłać do metody String.fromCharCode() aby uzyskać naciśnięty klawisz.

	function klawisz(e) {
		if (!e) var e = window.event;
		if (e.keyCode) return e.keyCode;			
	}

	document.getElementById('poleTekstowe').onkeydown = function(e) {
		alert('Naciśnięty klawisz to: ' + String.fromCharCode(klawisz(e)) )
	}
	

Wstrzymanie domyślnej akcji

Większość elementów na stronie wykonuje domyślne akcje. Linki przenoszą w jakieś miejsca, formularz się wysyłają itp. Po podpięciu zdarzeń pod obiekt będą ono wywoływane na początku, jednak zaraz po nich wykonana zostanie domyślna czynność.
Aby temu zapobiec skorzystamy z metody e.preventDefault():

        element.addEventListener('click',function (e) {
            alert('Ten link nigdzie nie przeniesie.');
            e.preventDefault();
        },false);
    

Niektórych zdarzeń nie da się w ten sposób zatrzymać (np. load), o czym mówi nam właściwość e.cancelable

Zatrzymanie nasłuchu innych zdarzeń

Po odpaleniu zdarzenia, domyślnie przechodzi ono po obiektach od dołu hierarchii do góry - dążąc do dokumentu.
Spójrzmy na poniższy przykład:

    <div id="jakis_div1">
        <a id="link_w_div1" href="">Kliknij mnie</a>
    </div>
    
        var div = document.getElementById('jakis_div1');
        var a = document.getElementById('link_w_div1');

        div.addEventListener('click',function (e) {
            alert('Kliknięto div');
        },false);

        a.addEventListener('click',function (e) {
            alert('Kliknięto link');
        },false);
    
Kliknij mnie

Jeżeli teraz klikniemy na link zostanie wyświetlony komunikat "Kliknięto link". Równocześnie jednak zostanie wyświetlony komunikat dla div!. Dzieje się tak dlatego, że wszystkie zdarzenia zachowują się jak bąbelki. Po wywołaniu biegną one do góry hierarchii dokumentu. Nasze zdarzenie click dla linka zostaje odpalone, a następnie biegnie do początku hierarchii natrafiając po drodze na kolejny nasłuch wywołany przez div.

Aby przerwać tą wędrówkę oraz kolejne nasłuchy, skorzystamy z metody e.stopPropagation():

                
        var div = document.getElementById('jakis_div2');
        var a = document.getElementById('link_w_div2');

        div.addEventListener('click',function (e) {
            alert('Kliknięto div');
        },false);

        a.addEventListener('click',function (e) {
            alert('Kliknięto link');
            e.preventDefault();
            e.stopPropagation();
        },false);
    

Od tej pory kliknięcie na link wywoła zdarzenie tylko dla tego linku.

Kliknij mnie

Dodatkowe przykłady i informacje o bąbelkach możesz zobaczyć na stronie o obsłudze myszki.