Praktyczne wprowadzenie do JavaScript #14

W JavaScript, podobnie jak w HTML, czy też CSS, występuje kilka sposobów na osiągnięcie danego efektu, o czym między innymi przekonaliśmy się w siódmej części. Spowodowane jest to w głównej mierze niejednolitą interpretacją standardów sieciowych. Prym w tej dziedzinie wiedzie Internet Explorer, gdzie skrypty działające na przeglądarkach typu Gecko czy też Opera potrafią nierzadko odmówić współpracy. Microsoft przemycił również pewne własne rozwiązania w implementacji JS, które co prawda działają na innych przeglądarkach, ale są bardzo nieużyteczne i co najważniejsze, niezgodne z zasadami budowania dokumentów XHTML. Dzisiaj omówimy większość z nieścisłości wynikających z powyższych faktów. Ponadto uzupełnisz swoje fachowe słownictwo o nowe terminy, które pozwolą Tobie śmielej poruszać się w świecie JavaScript.

Dotychczas świadomi byliśmy faktu, że operujemy kodem JavaScript na dokumentach HTML. Zgadza się. Znamy również wiele funkcji pozwalających działać na nich, jak choćby najbardziej kojarzoną – document.getElementById(). Wymieńmy wszystkie, które dotąd poznaliśmy, a które bezpośrednio lub pośrednio pozwalają nam manipulować na tagach:

DOM
(ang. Document Object Model, obiektowy model dokumentu)
to API dla dokumentów HTML i XML. Wprowadza strukturalne odwzorowanie dokumentu, pozwalając modyfikować jego zawartość i warstwę prezentacji. Podsumowując, DOM łączy strony WWW ze skryptami lub innymi językami programowania.

Wszystkie powyższe funkcje to składniki DOM, grupy funkcji i właściwości bezpośrednio związanych z operacjami na tagach dokumentów HTML, udostępnionych w JavaScript. DOM to po prostu zestaw narzędzi, które w jedyny słuszny sposób pomagają nam w pracy z dokumentami XML i XHTML. To dzięki niemu dodawanie nowych tagów, zmiana jego atrybutów czy też ich usuwanie nie stanowią dla nas problemów.

Zapamiętaj drogi Czytelniku, że DOM korzysta w głównej mierze z drzewiastej budowy dokumentów XHTML. Popatrzmy:

<div id="parent">
	<p>
		<span>Akapit</span>
	</p>
</div>
Poruszając się w świecie DOM możesz też spotykać się z nazywaniem dzieci gałęźmi(ang. node). Są to pojęcia tożsame.

W powyższej konstrukcji to <div> o id=”parent” jest rodzicem dla paragrafu <p>, który z kolei pełni tę samą rolę dla elementu <span>. Na tej zasadzie opiera się wiele funkcji i właściwości elementów należących do DOM, których większą ilość poznasz już wkrótce. Idąc tym tropem będziemy mogli w łatwy sposób dostać się do rodziców danych tagów, do ich dzieci, do pierwszego dziecka. Dla przeciwwagi, potrafimy już dodać nowe dziecko danemu rodzicowi, a robi to za nas funkcja appendChild:


var uchwyt = document.getElementById('parent');
var div = document.createElement('div');
uchwyt.appendChild(div);

W ten sposób nasza drzewiasta struktura wzbogaci się o jedną gałąź div, którą utworzyliśmy przy pomocy funkcji DOM document.createElement():

<div id="parent">
	<p>
		<span>Akapit</span>
	</p>
	<div></div>
</div>

Dzięki setAttribute możemy nadać dowolny atrybut nowoutworzonemu elementowi przed jego dodaniem do rodzica. Niech to będzie id=”now_div”:

var uchwyt = document.getElementById('parent');
var div = document.createElement('div');
div.setAttribute('id', 'nowy_div');

uchwyt.appendChild(div);

Co zwróci nam w postaci HTML następujący kod:

<div id="parent">
	<p>
		<span>Akapit</span>
	</p>
	<div id="nowy_div"></div>
</div>

Gdyby jednak przestał nam odpowiadać identyfikator nowego elementu blokowego w dokumencie, z łatwością możemy go usunąć – po dodaniu do rodzica, czy przed, to nie stanowi zupełnie żadnej różnicy.

var uchwyt = document.getElementById('parent');
var div = document.createElement('div');
div.setAttribute('id', 'nowy_div');

uchwyt.appendChild(div);

div.removeAttribute('id');

Gdybyśmy chcieli nadać jakąś wartość nowemu tagowi div, zapewne użylibyśmy do tej pory innerHTML:

var uchwyt = document.getElementById('parent');
var div = document.createElement('div');
div.setAttribute('id', 'nowy_div');
div.innerHTML = 'Jakas tresc';

uchwyt.appendChild(div);

Nie jest to jednak prawidłowa metoda! Dlaczego jej używaliśmy? Powodów jest kilka. Musisz wiedzieć, że właściwość ta jest niezgodna ze standardami DOM. innerHTML to wytwór wyobraźni programistów przeglądarki Internet Explorer, którzy w łatwy sposób udostępnili możliwość zmiany zawartości tagów w dokumencie. Co więcej, wiele programistów JavaScript, nieświadoma minusów tego rozwiązania, szybko zaadoptowała go do własnych skryptów. W ten sposób, wbrew wszelkiej logice, niewłaściwy innerHTML jest bardzo często spotykany na wielu stronach WWW. Byłoby trochę nie fair, gdybyś go, przezornie, nie znał. Po drugie, jak sam rozumiesz drogi Czytelniku, bombardowanie Ciebie już na początku przygody z JavaScript wiadomościami, która przeglądarka interpretuje dany kod, a która nie, co jest dobre, a co trochę gorsze, spowodowałaby niepotrzebny mętlik w Twojej głowie.

Rozpatrzmy działanie tej konstrukcji na podanym wyżej przykładzie, gdzie dodawaliśmy nowy tag do drzewa dokumentu HTML. Przy pomocy innerHTML wyglądałoby to tak:

var uchwyt = document.getElementById('parent');

uchwyt.innerHTML += '<div id="nowy_div"></div>';

W ten sposób, do zawartości głównego rodzica, przypisalibyśmy z prawej strony, czyli od końca (o czym świadczy +=) elementowi o id=”parent” wartość <div id=”nowy_div”></div>.

Co jest więc złego w innerHTML? Szczerze mówiąc, drogi Czytelniku, tak naprawdę odpowiedź na to pytanie jest trudna. Musisz wiedzieć, że nie ma istotnych różnic w stosowaniu innerHTML i appendChild w duecie z createElement. Większość szanowanych zagranicznych jak i polskich bloggerów podjęło ten temat już bardzo dawno, jednak nikt nie jest w stanie podać stosownych i w pełni przekonywujących argumentów, dlaczego nie warto stosować innerHTML. Jest za to kilka zalet i wad tego rozwiązania, które w bezpośredniej konfrontacji wydają się równoważyć.

Za innerHTML:

Przeciw innerHTML:

Jak widzisz, spór o innerHTML będzie tak długo żywy, jak długo przeglądarki będą go obsługiwały. Mimo że jest to konstrukcja niezgodna ze standardami, bardzo przydaje się, kiedy chcemy uzupełnić zawartość danego tagu o długi kod. W takim wypadku innerHTML to wręcz zbawienie.

My z kolei, jako że chcemy uczyć się tylko poprawnych i eleganckich praktyk, innerHTML będziemy od tej pory używać bardzo rzadko.

Jako że język JavaScript oferuje wiele metod rozwiązania problemu, modne są różne kontrukcje, używane przez programistów, które nie są jednak zgodne z obowiązującymi standardami i semantyką.

Warto więc powiedzieć coś o używaniu w linkach atrybutu href=”#” w połączeniu z zdarzeniem onclick. Co prawda użycie tego typu konstrukcji nie przeszkadza w wykonywaniu się skryptu, aczkolwiek jest bardzo nieużyteczne, kiedy użytkownik ma wyłączoną obsługę JavaScript. Wtedy, zamiast przejść do strony informującej go, że dany link wymaga obsługi JS, bądź strony zastępczej, która robi to samo, co skrypt JS, ale po stronie serwera, użytkownik pozostaje zdezorientowany. Dlatego należy stosować poprawne adresy stron w linkach:

<a href="jakis_link.html" id="link">klik!</a>

A w skryptach blokować otwieranie się linku, np. return false;. Poza tym należy pamiętać, że popularną kotwicę w linkach należy stosować w celu odniesienia się do konkretnego elementu na stronie o określonym id. Częste jest więc użycie:

<a href="#getComments" id="link">klik!</a>

które jest prawidłowe, ponieważ po kotwicy występuje jakaś wartość identyfikatora. Pusta kotwica gryzie się natomiast ze standardami i elegancją kodu.

Kolejny spór, który determinuje w głównej mierze zgodność z obowiązującymi standardami, tyczy się dodawania zdarzeń.

Dotychczas, aby nadać zdarzenie tagom HTML używaliśmy konstrukcji

window.onload

oraz

uchwyt.zdarzenie = jakas funkcja;

Są to metody dość eleganckie, ale jak można wywnioskować, tęgie głowy odpowiadające za standardy sieciowe wymyśliły coś znacznie innego. Otóż DOM posiada również funkcje, pozwalające nadać, jak i usunąć danemu elementowi dowolne zdarzenia. Mowa o addEventListener oraz removeEventListener. Porównajmy działanie funkcji na przykładzie onclick.

Stary sposób:

<!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>Klik</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />

<!-- Nasz kod Javascript: -->

<script type="text/javascript">


	function Laduj()
	{
	
		var uchwyt = document.getElementById('link');
		uchwyt.onclick = Kliknij;
	}

	function Kliknij()
	{
		alert('Klik!');
	}

	window.onload = Laduj;
	
</script>
</head>
<body>
	<a href="#" id="link">klik!</a>
</body>
</html>

Sposób z addEventListener:


<!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>Klik</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />

<!-- Nasz kod Javascript: -->

<script type="text/javascript">

	

	function Laduj()
	{
	
		var uchwyt = document.getElementById('link');
		uchwyt.addEventListener("click", Kliknij, false);
	}

	function Kliknij()
	{
		alert('Klik!');
	}

	window.onload = Laduj;
	
</script>
</head>
<body>
	<a href="#" id="link">klik!</a>
</body>
</html>

Funkcja addEventListener przybiera, jak widać, trzy argumenty:

W naszych przykładach, w których porównujemy działanie addEventListener ze starym sposobem, mamy nadal zapis window.onload. Pora go także zmienić na nowopoznaną, DOM’ową.

<!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>Klik</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf8" />

<!-- Nasz kod Javascript: -->

<script type="text/javascript">

	

	function Laduj()
	{
		var uchwyt = document.getElementById('link');
		uchwyt.addEventListener("click", Kliknij, false);
	}

	function Kliknij()
	{
		alert('Klik!');
	}

	window.addEventListener("load", Laduj, false);
	
</script>
</head>
<body>
	<a href="#" id="link">klik!</a>
</body>
</html>

Dodaliśmy następującą linijkę:

window.addEventListener("load", Laduj, false);

Odnośnikiem jest tutaj tym razem słowo window, co świadczy o tym, że funkcja doda zdarzenie dla całego dokumentu. Pierwszy argument przybrał wartość load, a drugi Laduj, z czego wynika, że po załadowaniu dokumentu wykona się funkcja Laduj właśnie.

Niestety, w tym wypadku mamy również pewien zgrzyt związany z przeglądarką Internet Explorer. Otóż upodobała sobie ona do dodawania zdarzeń inną funkcję o nazwie attachEvent. O problemach z tym związanych porozmawiamy już w następnych odcinkach.

Komentarze

1

Po pierwsze chciałem pogratulować naprawdę fajnych tutoriali :)

Ale właściwie to miałem taką małą wątpliwość: wydaje mi się, że appendchild (zgodnie z nazwą) dodaje element wewnątrz obiektu, na którym jest wywoływany, w przykładach byłoby tak:

Akapit

Ake oczywiście mogę się mylić… :)
Pozdrawiam

yaqoob
2

Hmm.. małe problemy z wklejaniem HTMLa – w każdym razie div id=”nowy_div” miał być wewnątrz div id=”parent”

yaqoob
3

Dokładnie yaqoob, male przeoczenie, juz poprawilem, dzieki :-)

4

jak zawsze nie ma sie do czego doczepic :) czekam na kolejne kursy

Paweł
5

[…] 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ą […]

6

[…] paragrafu <p id=”paragraph”>, czyli elementu drzewa HTML. Jak wiemy natomiast z 14. części Praktycznego wprowadzenia, na elementach drzewa HTML operują funkcje skupione w tzw. DOM […]

7

pokazałeś alternatywe dla inner w przypadku gdy chcemy dodac obiekt-dziecko, ale gdy chce tylko zmienić tekst to czego mam uzyc jako zamiennika?

JS
8

createTextNode, bedzie o tym wkrotce :-)

9

jescze tylko zapamietac gdzie stawaic apostrof :)

Dodaj komentarz

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