Praktyczne wprowadzenie do JavaScript #16

Przykład z poprzedniego odcinka działa w najlepsze, jednak techniki programistyczne, jakich użyliśmy nie są idealne. Aby być w pełni zgodnym z DOM, musimy zmienić sposób dodawania zdarzeń – wykorzystamy w tym celu specjalnie do tego stworzoną funkcję addEventListener.

W zasadzie wszystko byłoby w porządku, gdyby nie to, że addEventListener nie działa w Interner Explorer. Analogiczną funkcją w tej przeglądarce jest attachEvent. Należy więc zbudować jedną funkcję, która w zależności od przeglądarki będzie dodawać zdarzenie przy pomocy attachEvent, bądź addEventListener. Kwestią problematyczną jest to, jak rozpoznać, że użytkownik korzysta z IE lub innej przeglądarki, tak, by dobrać odpowiednią funkcję dodawania zdarzeń.

Musimy rozpatrzyć dwie możliwości:

Pierwsze, co rzuca się na myśl, to sprawdzenie nazwy przeglądarki. Powiedzmy, że pod zmienną nav kryłaby się jej nazwa. Zmienna nav mogłaby więc mieć wartość Opera, Mozilla etc. Musielibyśmy sprawdzić wtedy używaną przeglądarkę w podobny sposób:

	if (nav == "IE")
	{
		// kod attachEvent
	}
	else
	{
		// kod addEventListener
	}

Z powyższego kodu wynika, że jeśli zmienna nav miałaby wartość IE, wykonałaby się funkcja attachEvent. W przeciwnym razie uruchomiłaby się addEventListener.

W zasadzie ten kod wydaje się dość prosty i trudno o uzyskanie czegoś bardziej optymalnego, by rozpoznać, jakiej przeglądarki używamy. Jest jednak duża wada tego rozwiązania, otóż nazwę przeglądarki pobieramy w ten sposób:

var nav = navigator.userAgent;

Wyświetlmy teraz pobraną wartość w okienku:

var nav = navigator.userAgent;
alert(nav);

Wystarczy sprawdzić efekt w dwóch przeglądarkach, by przekonać się, że otrzymywane wartości są dość skomplikowane, np.:

W takim razie, należałoby specjalnie obrobić zmienną nav, tak, by pozostała tylko nazwa samej przeglądarki. Jest to dość uporczywe zadanie, a jako że istnieje znacznie szybsza metoda, pominiemy je.

Otóż, jak dobrze wiemy, chcemy wykryć, kiedy użytkownik korzysta z IE, który nie obsługuje addEventListener. Jak wiadomo, funkcji tej, jak i attachEvent używamy w połączeniu z odnośnikiem do jakiegoś elementu, np.:

document.getElementById('link').addEventListener("load", Laduj, false);

Oczywiście to samo, można zapisać tak:

var odnosnik = document.getElementById('link');
odnosnik.addEventListener("load", Laduj, false);

Przyrzyjmy się temu zapisowi: odnosnik.addEventListener. odosnik jest tutaj zmienną, będącą referencją do elementu o id=”link”. addEventListener jest natomiast funkcją, którą można wykonać na tym elemencie. Świadczy o tym, jak już wiemy, kropka z lewej strony. We wszystkich przeglądarkach obsługujących DOM, coś takiego oczywiście wykona się bez żadnych błędów. Zauważmy jednak, że Internet Explorer w ogóle nie przewiduje takiej funkcji i w efekcie, nie znajduje jej we własnym silniku, aby ją uruchomić. Co za tym idzie, użycie addEventListener na podanym elemencie jest w IE niemożliwe, bo przeglądarka w swoim wachlarzu możliwych funkcji, nie ma żadnej o nazwie addEventListener. Aby więc sprawdzić, czy użycie addEventListener jest możliwe, należy użyć warunku if:

var odnosnik = document.getElementById('link');
if (odnosnik.addEventListener)
{
	// przegladarka interpretuje funkcje addEventListener
	odnosnik.addEventListener("load", Laduj, false);
}
else
{
	// przegladarka nie interpretuje funkcji addEventListener
	// kod dla attachEvent
}

Konkretnie chodzi nam o to:

if (odnosnik.addEventListener)
{

W ten sposób sprawdzamy, czy możliwe jest użycie funkcji addEventListener dla zmiennej odnosnik. Jeśli jest to prawda, wykona się kod w klamrze.

Równie dobrze możemy sprawdzić, czy można wykonać inną funkcję, np.:

if (odnosnik.attachEvent)
{

Powyższy kod sprawdzi, czy możliwe jest użycie funkcji attachEvent. Kod w klamrze wykona się tylko w Internet Explorer, bo tylko ta przeglądarka obsługują wspomnianą funkcję.

if (odnosnik.appendChild)
{

Sprawdzi natomiast, czy możliwe jest użycie funkcji appendChild, dodającej nowe elementy. W tym wypadku kod w klamrach wykona się w każdej przeglądarce.

Z analizy powyższych przykładów wynika, że aby sprawdzić, czy można wykonać daną funkcję w stosunku do jakiegoś elementu, należy użyć konstrukcji if według poniższego szablonu:

if (odnosnik_do_elementu.nazwa_funkcji) { }

Pora w takim razie zająć się przykładem z poprzedniego odcinka.

Oto, co pozostawiliśmy po sobie:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="pl-PL">
<head>
<title>Strona z formularzem</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />

<!-- Nasz kod Javascript: -->

<script type="text/javascript">

	var active;
	
	function Sprawdz()
	{
		if (this.value.length == this.getAttribute('maxlength'))
		{
			active = this.name;
			Next();
		}

	}
	function Next()
	{
		elements = document.forms['formularz'].elements;
		for (var i = 0;i < elements.length ; i++ )
		{
			if (elements[i].name == active)
			{	if (elements[i+1])
				{
					elements[i+1].focus();
				}
			
				
			}
		}
	}
	window.onload = function()
	{
		elements = document.forms['formularz'].elements;
		for (var i = 0;i < elements.length ; i++ )
		{
			
			if (elements[i].getAttribute('maxlength') < 50)
			{
				
				elements[i].onkeyup = Sprawdz;
			}
			
		}
	}

</script>

</head>
<body>
	<div>
		<form name="formularz">
		  <input type="text" name="jeden" maxlength="5" />
		  <input type="text" name="dwa" maxlength="5"  />
		  <input type="text" name="jeden2" maxlength="5" />
		  <input type="text" name="dwa2" maxlength="5"  />
		</form>
	</div>
</body>
</html>

Do wyeliminowania mamy nieelegancki zapis dwóch zdarzeń:

Zajmijmy się tym pierwszym. Otóż wszystko, co jest w klamrach po window.onload trzeba zapisać w oddzielnej funkcji, tak, by bez problemów można było użyć jej przy dodawaniu zdarzenia. Zróbmy to:

	function Laduj()
	{
		elements = document.forms['formularz'].elements;
		for (var i = 0;i < elements.length ; i++ )
		{

			if (elements[i].getAttribute('maxlength') < 50)
			{

				elements[i].onkeyup = Sprawdz;
			}

		}
	}

Nazwaliśmy ją Laduj, zwyczajowo zresztą, jeśli sledziłeś drogi Czytelniku poprzednie odcinki. Pora teraz dodać zdarzenie. Moglibyśmy to zrobić tak:

	if (window.addEventListener)
	{
		//przegladarka interpretuje funkcje addEventListener
		window.addEventListener("load", Laduj, false);
	}
	else
	{
		//przegladarka nie interpretuje funkcji addEventListener
		window.attachEvent("onload", Laduj);
	}

Jak widać, attachEvent przyjmuje dwa argumenty. Pierwszy z nich to nazwa zdarzenia. W przeciwieństwie do addEventListener należy ją podać z przedrostkiem on np. "onclick" (tutaj onload). Drugi argument to nazwa funkcji, jaka ma być wykonywana w danym zdarzeniu, podobnie jak w funkcji z DOM, podajemy ją bez żadnych apostrofów/cudzysłowów.

W przyszłości rozbudujemy naszą funkcję dodajZdarzenie o jedną, relatywnie istotną funkcję, aczkolwiek teraz nie zawracaj sobie tym głowy. Wszystko w swoim czasie.

Gdybyśmy jednak zapragnęli dodać wiele zdarzeń, tak by działały pod wszystkimi przeglądarkami, używanie podobnej konstrukcji zajęłoby sporo miejsca. Napiszmy więc jedną funkcję, obsługującą dodawanie zdarzeń dla IE i alternatywnych przeglądarek. Nazwijmy ją dodajZdarzenie. Funkcja ta przyjmie 3 argumenty:

  • nazwa zdarzenia, ważne - bez "on"
  • funkcja, przypisana do danego zdarzenia
  • Przykładowe wywołanie funkcji będzie więc wyglądać tak:

    dodajZdarzenie(window, "load", Laduj);

    Pora teraz zdefiniować naszą funkcję dodajZdarzenie.

    	function dodajZdarzenie(odnosnik, zdarzenie, funkcja)
    	{
    		if (odnosnik.addEventListener)
    		{
    			odnosnik.addEventListener(zdarzenie, funkcja, false);
    		}
    		else
    		{
    			odnosnik.attachEvent("on"+zdarzenie, funkcja);
    		}
    	}

    Jeśli więc wywołamy funkcję w ten sposób: dodajZdarzenie(window, "load", Laduj); to w jej ciele argumenty będą miały następujące wartości:

    Dalsza część funkcji wygląda bardzo podobnie do tego, co już mieliśmy. Warto jednak spojrzeć na część, która wykonywać będzie się tylko pod IE:

    else
    {
    	odnosnik.attachEvent("on"+zdarzenie, funkcja);
    }

    Wywołujemy tutaj attachEvent z dwoma argumentami, z któch jeden wypada omówić, a chodzi mianowicie o "on"+zdarzenie. W ten sposób doklejamy do wyrazu on wartość zmiennej zdarzenie. W efekcie, jeśli zmienna zdarzenie będzie miała wartość np. "click", funkcja attachEvent dostanie w pierwszym argumencie onclick, ponieważ dokleiliśmy do słówka on wartość tej właśnie zmiennej.

    Teraz, kiedy mamy już stosowną funkcję, wypada użyć jej w naszym skrypcie.

    Zamiast

    window.onload = function () { ..kod.. }

    użyjmy

    dodajZdarzenie(window, "load", Laduj);

    Jeśli chodzi o onkeyup sprawa będzie wyglądać następująco:

    dodajZdarzenie(elements[i], "keyup", Sprawdz);

    Jako że skrypt sam za nas doklei przedimek "on", nazwy zdarzeń podajemy bez niego, czyli: load i keyup.

    Jeśli chodzi o wywołanie funkcji dodajZdarzenie, warto też zwrócić uwagę, dlaczego jeden argument otoczony jest cudzysłowami, a drugi nie. Otóż nazwa zdarzeń jest ciągiem znakowym, a nie zmienną, więc deklarujemy ją właśnie z cudzysłowami. To tak, jak w wypadku alert:

    alert("Jakies zdanie");

    W tym wypadku podaliśmy jako argument ciąg znakowy, który wpisaliśmy z klawiatury.

    Natomiast poniżej:

    var zdanie = "Jakies zdanie";
    alert(zdanie);

    przekazujemy zmienną zdanie jako argument, więc nie otaczamy jej apostrofami ani cudzysłowami. Podobnie jest z użyciem dodajZdarzenie.

    Przyjrzyjmy się teraz całemu dokumentowi HTML:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="pl-PL">
    <head>
    <title>Strona z formularzem</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
    
    <!-- Nasz kod Javascript: -->
    
    <script type="text/javascript">
    
    	var active;
    
    	function dodajZdarzenie(odnosnik, zdarzenie, funkcja)
    	{
    		if (odnosnik.addEventListener)
    		{
    			odnosnik.addEventListener(zdarzenie, funkcja, false);
    		}
    		else
    		{
    			odnosnik.attachEvent("on"+zdarzenie, funkcja);
    		}
    	}
    
    	function Sprawdz()
    	{
    		if (this.value.length == this.getAttribute('maxlength'))
    		{
    			active = this.name;
    			Next();
    		}
    
    	}
    	function Next()
    	{
    		elements = document.forms['formularz'].elements;
    		for (var i = 0;i < elements.length ; i++ )
    		{
    			if (elements[i].name == active)
    			{	if (elements[i+1])
    				{
    					elements[i+1].focus();
    				}
    
    			}
    		}
    	}
    	function Laduj()
    	{
    		elements = document.forms['formularz'].elements;
    		for (var i = 0;i < elements.length ; i++ )
    		{
    
    			if (elements[i].getAttribute('maxlength') < 50)
    			{
    				dodajZdarzenie(elements[i], "keyup", Sprawdz);
    			}
    
    		}
    	}
    
    	dodajZdarzenie(window, "load", Laduj);
    
    </script>
    
    </head>
    <body>
    	<div>
    		<form name="formularz">
    		  <input type="text" name="jeden" maxlength="5" />
    		  <input type="text" name="dwa" maxlength="5"  />
    		  <input type="text" name="jeden2" maxlength="5" />
    		  <input type="text" name="dwa2" maxlength="5"  />
    		</form>
    	</div>
    </body>
    </html>

    Wszystko działa, jednak nie pod IE. Dlaczego? Otóż kolejnym problemem jest to, że Internet Explorer, przy powyższym sposobie dodawania zdarzeń (poprzez attachEvent) "nie widzi" this przez co odnoszenie się do wartości elementów formularza (w funkcji Sprawdz), które uzupełniamy, jest niemożliwe. Tym problemem zajmiemy się już w następnym odcinku.

    Komentarze

    1

    Właśnie skończyłam przyswajać 6. część twojego kursu. Już pisałam u Mousera, że jestem pod niemałym wrażeniem i z chęcią do niego wracam, ale…
    Brakuje mi bardzo nawigacji. Mam ogromną prośbę… Czy mógłbys na końcu każdego ‚odcinka’ wrzucić link do następnego? (Przejdź do kolejnej części kursu- albo coś w ten deseń). Niestety bardzo niewygodne jest przechodzenie za każdym razem do kategorii i klikanie w JS i zaznaczanie wczesniejszej strony i dopiero szukanie odcinka, o który chodzi – wybija mnie to z rytmu ;) i pewnie kilka innych osób też.
    Pozdrawiam

    2

    Wtyczka, którą zainstalowałem do linkowania odcinków mnie nie satysfakcjonowała, więc porzuciłem ten pomysł. Postaram się poszukać czegoś lepszego i uruchomić ponownie :-).

    3

    popieram pomysł aniorell

    Jarek
    4

    nie rozumiem jednej części, a że właśnie jej nie rozumiem nie wiem czy to bzdet czy coś co ma jakiś wpływ:
    w tej cześci wszedzie piszesz dodajZdarzenie(window, „load”, Laduj); a w części 14 zdarzenie zaczepiamy nie do window tylko do document… jak jest poprawnie?

    qb
    5

    Argh, moje przeoczenie, w zasadzie dzialalo tak samo, ale warto jednak trzymac sie jednego, czyli window… Dzieki za czujnosc qb, poprawione :)

    Pozdrawiam

    6

    Ah ten IE.

    ziko
    7

    Jak dodac funkcje ktora ma argumety bo jak tak pisze to nie dziala
    dodajZdarzenie(bleble, „click”, „funkcja(100,100,100);”);

    sebik90
    8

    dokladnie, to nie działą jezezli chce wywolac funkcje z argumentami, jak to obejsc? :<

    DewREW
    9

    Ponieważ musisz wywołać funkcję dodajZdarzenie w następujący sposób :

    dodajZdarzenie(element,”click”,function(){Powiedz(„raz”, „dwa”)});

    Levi
    10

    Polecam stosowanie zmiennej pomocniczej noIE = true;

    :D

    koe
    11

    przykład z części 10 (walidacja formularza) nie działa przy zastosowaniu funkcji dodajZdarzenie. Wie ktoś czemu i jak to obejść żeby nie zostawiać .onsubmit = funkcja? pozdrawiam

    Dodaj komentarz

    Dozwolone tagi: <blockquote>, <code>, <strong>