Uvod u JavaScript i DOM (Document Object Model)
Uvod
U prvom članku uvodnog serijala o web dizajnu naveli smo da jezici HTML, CSS i JavaScript predstavljaju tri najvažnija jezika koji se koriste u izradi web stranica, nakon čega smo se odmah upustili u proučavanje prva dva jezika i čitaocima preporučili da se izučavanju jezika JavaScript posvete tek pošto dobro savladaju osnove HTML-a i CSS-a.
Ukoliko čitate ove redove, pretpostavićemo da ste savladali osnove HTML-a i CSS-a, to jest, pretpostavićemo da ste procenili da je došao trenutak da počnete da proučavate JavaScript, i stoga ćemo u nastavku napraviti presek osnovnih mogućnosti ovog popularnog jezika.
Neko manje upućen, rekao bi možda da je JavaScript jezik koji se povezuje sa HTML-om i, figurativno (u prenesenom smislu), može se reći da je tako, ali, u pravom smislu reči - JavaScript se zapravo povezuje sa strukturom podataka koja nastaje iz HTML koda pri učitavanju dokumenta u browser, s tim da takva struktura (za razliku od HTML-a), nije obična niska znakova, već, posebno dizajnirano stablo.
Pomenuta struktura nosi naziv Document Object Model, ili skraćeno - DOM.
Document Object Model (DOM)
Sadržaj web stranica prenosi se od servera do klijenta u obliku HTML koda, međutim, koliko god tekst bio prikladan format za daljinski prenos informacije o strukturi sajta, podaci koji su neophodni za prikaz stranice, ne skladište se u browserima u izvornom obliku. *
Struktura primljenog HTML dokumenta koristi se za izgradnju strukture stabla ....
.... koje nadalje, u komunikaciji između web programera i browsera, predstavlja strukturu stranice.
U korenu DOM stabla nalazi se "dokument" (figurativno - browser), odmah ispod je čvor koji predstavlja <html>
tagove, na sledećem hijerarhijskom nivou su čvorovi koji predstavljaju tagove <head>
i <body>
, a na ostalim hijerarhijskim nivoima nalaze se ostali tagovi, poređani u raspored koji odgovara rasporedu tagova u izvornoj HTML datoteci.
Svakom čvoru pripisan je i odgovarajući sadržaj.
Da bismo iznete principe razumeli što bolje, razmotrićemo sledeći jednostavan HTML kod ....
.... i DOM stablo koje nastaje iz prikazane HTML strukture:
Stablo sadrži sve bitne informacije iz ulaznog HTML dokumenta (tekstualni sadržaj svih tagova i vrednosti svih atributa), i - što je najvažnije - u pitanju je struktura koja omogućava znatno brži i znatno efikasniji pristup elementima.
Za pristup elementima DOM stabla (i obavljanje različitih operacija nad elementima), koristi se jezik JavaScript.
Osnove jezika JavaScript
JavaScript je skriptni programski jezik visokog nivoa koji omogućava: pristup elementima stranice (preko DOM modela), obradu (odnosno izmenu) elemenata, asinhronu komunikaciju sa serverom preko specijalizovanih XmlHttpRequest
objekata (čime ćemo se baviti u jednom od budućih članaka), kao i obavljanje brojnih drugih zadataka.
Sam jezik pojavio se sredinom devedesetih godina dvadesetog veka i u početku je nudio znatno manje mogućnosti nego danas, ali, tokom vremena, mogućnosti jezika su proširivane, i JavaScript je postepeno stekao veliku popularnost (i status jedne od najvažnijih mrežnih tehnologija).
Na popularnost JavaScript-a posebno je uticala pojava radnog okruženja Node JS (cca. 2009), koje je omogućilo pokretanje desktop programa napisanih u JavaScript-u, kao i korišćenje JavaScript-a u vidu back-end tehnologije, što prethodno nije bio slučaj (JavaScript je bio jezik koji se koristi samo u browserima).
JavaScript sadrži programske strukture kakve se sreću u C-u i srodnim programskim jezicima (matematički operatori, operatori poređenja, uslovne naredbe, petlje, funkcije, objekti i sl), ali - kada se pokreće u browseru - JS ne omogućava pristup sistemskim resursima računara klijenta.
Redosled koji ćemo pratiti pri upoznavanju sa osnovama JavaScript-a je sledeći:
- povezaćemo HTML datoteku sa JS skriptom
- upoznaćemo se sa osnovnim komandama za ispis poruka i učitavanje korisničkog unosa
- razmotrićemo kako se (preko pripisanog id-a) pristupa elementima DOM strukture (odnosno, 'HTML tagovima'), nakon čega ćemo - preko primera - sagledati kako sve funkcioniše u praksi
- napravićemo kraći pregled osnovnih kontrolnih struktura (tj. upoznaćemo se sa različitim vrstama grananja i petlji)
Ostatak članka iskoristićemo za upoznavanje se drugim metodama za pristup DOM elementima (tj. praktično, HTML tagovima), kao i drugim sitnijim 'tehnikalijama'.
Povezivanje sa HTML datotekama
Povezivanje JavaScript kodova sa HTML datotekama, tipično se obavlja preko <script>
tagova, najčešće uz navođenje naziva datoteka u kojima je zapisan Javascript programski kod (pri čemu datoteke imaju ekstenziju .js
).
Za potrebe primera u ovom članku, tagove <script>
ćemo smestiti unutar <body>
tagova - na samom kraju (posle svih ostalih sadržaja, onako kako smo već videli).
Inače (tj. u drugim skriptama), <script>
tagovi se mogu smestiti i unutar <head>
tagova, što je zapravo pristup koji se tipično koristi (ali, objasnićemo u nastavku u čemu je razlika, i zašto smo za ovaj članak napravili drugačiji izbor).
Za početak (kako i dolikuje), napisaćemo "hello world" program (koji je svakako vrlo jednostavan), međutim, kada je preko JavaScript-a potrebno ispisati poruku korisniku, postavlja se pitanje - "gde će tekst biti ispisan".
Javascript nudi nekoliko osnovnih opcija za ispis, a mi ćemo se za početak upoznati sa "najdrastičnijom" opcijom (čim pokrenemo skriptu - razumećete na šta mislimo). :)
Ukoliko unutar JS dokumenta koji je povezan sa HTML datotekom, unesemo sledeći kod (i potom pokrenemo skriptu) ....
.... nećemo (naizgled) biti "prevelike sreće", jer - iako je poruka uredno ispisana - skripta je zapravo poništila prethodnu strukturu već učitanog HTML dokumenta!
U praktičnom smislu (budući da je dokument ionako bio 'prazan' (skoro)), moglo bi se reći da nismo napravili grešku 'koja skupo košta', međutim, najvažnije je da iz prvog "eksperimenta" - izvučemo pouku.
Kao što smo naveli, JavaScript ima direktan pristup DOM strukturi, a preko skripte koju smo napisali, direktno smo pristupili korenom elementu DOM strukture ....
.... i zadali smo (novi) sadržaj ....
.... čime je postojeći sadržaj praktično poništen.
Dakle, potrebno je da budemo pažljivi i precizni!
Ukoliko želimo da ispisujemo poruke na stranici - a pri tom ne želimo da postojeći sadržaj stranice bude "poništen" - potrebno je pristupiti određenom (konkretnom) HTML elementu na stranici - kome se potom može pripisati HTML sadržaj.
Međutim, ako je samo potrebno ispisivati "servisne" poruke, postoje i drugi načini (i u pitanju su opcije koje ćete koristiti prilično često).
Ispis poruka (alert i console.log)
Umesto "poništavanja" sadržaja DOM strukture (kao u prethodnom odeljku), i pre nego što razmotrimo kako se postojećim elementima može pripisivati HTML sadržaj, * razmotrićemo još i načine za slanje poruka korisniku preko funkcija alert
i console.log
.
Funkcija alert
....
.... pokreće otvaranje prozora sa porukom koja je predata kao argument, pri čemu je bitno uočiti da prozor (koji se otvara), blokira pristup elementima stranice (naravno, dok se prozor ne zatvori).
Za probu, kliknite na donje dugme:
Funkcija alert
tipično se koristi za ispis "kritičnih" poruka (poruke o sadržajima koji su nedostupni i sl), ali, u pojedinim situacijama se koristi i za poruke opšteg tipa (na primer, ako JavaScript koristite za kreiranje igara za browser, možete korisnicima prikazivati poruke o pređenom nivou/završenoj igri i sl).
Kao što smo ranije naveli, za ispis poruka mogu se koristiti i postojeći HTML elementi na stranici; po mogućnosti, elementi koje smo unapred odredili za ispis poruka (kao što ćemo videti uskoro), ali, poruke se mogu ispisivati i u konzoli browsera, preko funkcije log
:
Takođe, ukoliko smatramo da o greškama ne treba direktno obavestiti korisnika preko funkcije alert
- ali smatramo da je potrebno na drugi način prikazati informaciju o tome da je do greške došlo - poruke o greškama se mogu proslediti i preko konzole:
Razlika u odnosu na funkciju console.log
je u tome što se poruka koja je ispisana preko funkcije console.error
formatira crvenom bojom (tamno crvena slova i crveni okvir).
Za isprobavanje funkcija console.log
i console.error
, možete iskoristiti donje dugme:
(Nemojte zaboraviti da otvorite konzolu (preko prečice F12
), da biste mogli da vidite rezultat.)
Učitavanje korisničkog unosa (prompt)
Ukoliko je potrebno da korisnik unese podatke u skriptu, zadatak se može obaviti preko funkcije prompt
, koja iscrtava prozor sa poljem za korisnički unos (a korisnički unos se potom može učitavati u promenljive).
Pogledajmo primer:
Za isprobavanje primera, kliknite na donje dugme:
Kao što vidimo, povratna vrednost funkcije prompt
je niska - koja se predaje promenljivoj koju smo sami deklarisali
Postoje (naravno) i drugi načini za unos podataka (sa kojima ćemo se postepeno upoznavati), a na ovom mestu pomenućemo još jedan: probajte da unesete dve komande sa gornje slike, direktno u ulazno polje konzole browsera (još jedna korisna opcija koja, vrlo često - baš dobro dođe).
Nakon isprobavanja - vreme je da počnemo da povezujemo JS skripte sa elementima stranice (preko DOM stabla). :)
Pristup DOM elementima preko id-a (getElementById)
Najuobičajeniji način pristupa elementima, podrazumeva upotrebu pripisanih id-ova, a budući da elementi koje smo prethodno definisali nemaju pripisane id-ove, to je prvo što ćemo prepraviti ....
.... pri čemu će se i DOM struktura takođe promeniti (prikazujemo samo jedan pasus):
Sada se elementima može direktno pristupati preko JavaScript-a, a za primer ćemo prikazati kod preko koga se pristupiti prvom pasusu:
Koristili smo u svemu svojstvo innerHTML
, pri čemu tekst koji se unosi zaista predstavlja HTML kod (mogu se direktno pisati HTML tagovi, * čime se (praktično) definiše struktura elemenata kojima se pristupa).
Da bismo što bolje razumeli kako pristup elementima stranice funkcioniše u praksi, razmotrićemo jedan jednostavan primer, preko koga ćemo se upoznati i sa promenljivama u JS-u (a upoznaćemo se okvirno i sa kontrolnim strukturama jezika). **
Promenljive (var) i primer pristupa elementima stranice
Primer koji ćemo prikazati, podrazumeva dodavanje <div>
elemenata u DOM stablo, ali, budući da već znamo da prazni <div>
elementi nemaju "pojavni oblik" (sami po sebi), dodaćemo prvo prigodan selektor u datoteku stil.css
: *
.... posle čega se možemo vratiti na JavaScript i uneti sledeće izmene:
Promenljive se definišu preko rezervisane reči var
, ** a iz priloženog se može naslutiti i to da JavaScript spada u kategoriju jezika sa dinamičkom tipizacijom (pri deklaraciji promenljivih, ne navodi se tip promenljive, već se tip određuje automatski pri prvoj naredbi dodele).
Možemo takođe zapaziti i vrlo "liberalno" povezivanje string konstanti (običnog teksta koji je 'ručno' ispisan u programskom kodu), i vrednosti promenljivih brojčanog tipa (mislimo pri tom na sledeći odeljak iz prethodnog primera):
U toku izvršavanja skripte (to jest, u situacijama kada se ukaže potreba), vrednosti brojčanih promenljivih se automatski pretvaraju u tekst - što nije slučaj sa većinom drugih jezika (koji zahtevaju da se komanda za pretvaranje broja u nisku navede eksplicitno).
Pre nego što nastavimo, pogledaćemo kako primer koji smo prethodno definisali funkcioniše u praksi (kliknite na dugme):
Preko primera koji smo prethodno razmotrili, videli smo na delu izvestan broj naredbi i for
petlju, a u nastavku ćemo se detaljnije upoznati i sa ostalim najčešće korišćenim kontrolnim strukturama.
Kontrolne strukture jezika (grananja i petlje)
Kao i u drugim programskim jezicima, grananja i petlje omogućavaju:
- preusmeravanje toka programskih instrukcija shodno zadatim uslovima (grananja)
- ciklično ponavljanje određenih naredbi (petlje)
Grananje - if
Grananja u JavaScript-u (kao i u većini drugih programskih jezika), podrazumevaju prelazak na jedan od dva moguća * bloka naredbi - shodno postavljenom uslovu.
Prikazaćemo primer preko koga se proverava da li je uneti broj veći od nule (sa posebnim ispisima za situacije kada korisnik unese: pozitivan broj, nulu ili negativnu vrednost).
Primer takođe prikazuje da je uslove moguće i "ugnežđavati".
Za razliku od jednostavnog ugnežđavanja (sa kakvim smo se sreli u prethodnom primeru), višestruko ugnežđavanje koje bi bilo zapisano po istom principu - ni iz daleka ne predstavlja pregledan programski kod.
Za situacije kada je potrebno "ugnežđavati" više uslova, JavaScript nudi mogućnost korišćenja konstrukcije else if
, preko koje se ugneždavanje svodi (u sintaksičkom smislu), na nadovezivanje.
Uzmimo za primer skriptu koja treba da ispiše ocenu (od 1 do 5), shodno unetom broju bodova (koji mogu biti u rasponu od 0 do 100):
Međutim, u situacijama kada se ispituje striktno poklapanje sa jednom brojčanom vrednošću, tipično se koristi konstrukcija switch
.
Grananje - switch
Shodno svom "C-ovskom sintaksičkom poreklu", JavaScript nudi mogućnost korišćenja konstrukcije switch
, koja se tipično koristi za uparivanje pojedinačnih uslova - i instrukcija koje je potrebno izvršiti (naravno, ako je neki od uslova zadovoljen).
Kao primer, razmotrićemo switch
grananje koje, za uneti broj, ispisuje dan u nedelji koji odgovara unetom broju (ili poruku o grešci, ukoliko se unese broj koji je manji od 1 ili veći od 7):
Petlja - while
Petlja while
pokreće blok sa programskim instrukcijama - sve dok je zadati uslov zadovoljen:
- ukoliko je uslov zadovoljen, ulazi se u ciklus petlje
- izvršava se blok naredbi
- po završetku bloka naredbi - skripta se vraća na proveru uslova
Za primer ćemo uzeti petlju koja ispisuje brojeve u rasponu od 1 do n.
U praktičnom smislu, veoma je bitno da se unutar petlje unese naredba preko koje se koriguje vrednost promenljive koja je deo uslova (tako da u prigodnom trenutku uslov prestane da važi), jer - u suprotnom - nastaje "beskonačna petlja"!
Ukoliko je potrebno prekinuti izvršavanje ciklusa - pre nego što osnovni uslov poprimi vrednost false
- može se koristiti naredba break
(pogledajmo primer):
Primer koji smo prikazali predstavlja petlju koja se izvršava n
puta, pri čemu se u svakom ciklusu učitava vrednost (x
) i dodaje na sumu (s
), ali - samo pod uslovom da dodavanjem vrednosti neće biti prekoračena unapred zadata kontrolna vrednost, tj. 'granica' (promenljiva g
).
Petlja - do-while
Petlja do-while
, naizgled je slična "običnoj" while
petlji (sa kojom smo se upoznali u prethodnom odeljku), a razlika je u tome što se ovoga puta bezuslovno ulazi u prvi ciklus petlje.
Naravno, po izvršavanju ciklusa ispituje se uslov i - ukoliko je uslov zadovoljen - ponovo se ulazi u ciklus petlje (i postupak se ponavlja).
Za primer možemo uzeti do-while
petlju u okviru koje korisnik unosi brojčanu vrednost, sve dok ne pogodi zamišljeni broj (u rasponu od 1 do 1024):
Za isprobavanje primera, kliknite na donje dugme:
Primer koji smo videli, ne spada (naravno) u kategoriju programa koji pomažu u rešavanju svakodnevnih zadataka, ali, sasvim uspešno prikazuje zašto je vrlo praktično da programski jezici raspolažu strukturom petlje kod koje se blok naredbi izvršava "bar jednom" (a uslov se ispituje tek na kraju).
Takođe, dodatno smo isprobali naredbu prompt
i upoznali se sa naredbom parseInt
preko koje se niska pretvara u celobrojnu vrednost (naravno, pod uslovom da je format niske odgovarajući).
Petlja - for
Petlja for
predstavlja (kao i u drugim jezicima), praktičan način za organizaciju ciklusa koji se temelje na određenom broju ponavljanja (pri čemu je broj ponavljanja vrlo često poznat unapred).
Opšta šema for
petlje može se opisati na sledeći način:
U praktičnom smislu - for
petlja je isto što i while
petlja - "zapisana na malo drugačiji način".
Pri upotrebi bilo koje pravilno definisane while
petlje, podrazumeva se da je promenljiva od čije vrednosti zavisi izvršavanje petlje inicijalizovana pre ulaska u petlju, a posebno se podrazumeva da unutar tela petlje mora postojati naredba preko koje se vrednost navedene promenljive koriguje.
Na primeru while
petlje sa slike #19 (ispis brojeva od 1 do n) ....
.... možemo zapaziti da je promenljiva i
inicijalizovana pre ulaska u petlju (isto važi i za promenljivu n
, ali, ovoga puta je bitno da promenljiva n
ne menja vrednost tokom izvršavanja petlje), i možemo zapaziti da u telu petlje postoji naredba preko koje se vrednost promenljive i
menja (u pitanju je naredba na kraju tela petlje: ++i
).
Preko for
petlje, sve što smo naveli - zapisuje se "u jednom redu".
Ako ponovo za primer uzmemo program koji ispisuje vrednosti od 1 do n, možemo definisati sledeću for
petlju:
Na ovom mestu vraćamo se na specifičnosti JavaScript-a (to jest, na primer sa generisanjem HTML koda, koji smo prethodno razmatrali).
Pažljivijim sagledavanjem primera, mogli bismo zaključiti da se JS kod koji je zapisan izvan funkcija poziva pri pokretanju skripte (svaki put), što može biti upravo ono što želimo da izvedemo, ali - može biti i nešto što je potrebno izbeći.
Kod zapisan unutar funkcije, izvršava se samo kada se funkcija pozove, a možemo (naravno) udesiti i to da se pozivi funkcija poklope sa određenim događajima na stranici.
Događaji (events) i funkcije
Za primer ćemo uzeti tipičnu situaciju u kojoj je potrebno pokrenuti određene naredbe kada korisnik klikne na dugme (što će biti registrovano preko događaja onclick
):
U konkretnom primeru, svaki put kad korisnik klikne na dugme, biće pozvana funkcija dodavanjePasusa
.
Da bi "klik" imao (pravog) efekta, unutar skripte ćemo definisati funkciju dodavanjePasusa
(pri čemu ćemo premestiti celokupan kod koji smo ranije definisali - u telo nove funkcije):
Kada je u pitanju definisanje funkcija, primećujemo (očekivano), očiglednu sličnost sa programskim jezikom C (s tim da kod (očigledno) nije istovetan: na mestu gde se u C-u ili C++-u pojavljuje tip podatka koji funkcija vraća, u JavaScript-u se pojavljuje rezervisana reči function
).
Kada su u pitanju funkcije koje vraćaju vrednosti i/ili primaju argumente, stvari (takođe) funkcionišu slično kao u C-u, ali, sa tom razlikom što se parametri navode samo imenom, bez navođenja tipa podatka. *
U svakom slučaju, skripta koju smo prethodno napisali, sada je spremna, i neće se pokretati "sama od sebe" (već samo onda kada kliknemo na dugme).
Pozicija script tagova unutar HTML dokumenta
Pozicija <script>
tagova unutar HTML dokumenta - i te kako ima uticaja na korektnost izvršavanja JS koda, budući da se HTML tagovi učitavaju redom, što (razume se) važi i za <script>
tagove, odnosno, za JS kod koji se učitava preko <script>
tagova - i izvršava se odmah po učitavanju.
Ako bismo u gornjim primerima (u kojima se koristi funkcija getElementById
), tagove <script>
smestili unutar tagova <head>
, JavaScript kod bi bio učitan i pokrenut odmah (budući da je zapisan izvan funkcija), i stoga bi sledeća naredba ....
.... praktično pozivala element koji (još uvek) ne postoji!
Dakle: ako se funkcija getElementById
poziva unutar programskog koda koji se automatski pokreće pri učitavanju stranice, takav kod mora se pojaviti posle HTML koda kojim se definiše traženi element (u većini situacija je najpraktičnije da to bude - onako kako smo videli - na samom kraju unutar <body>
tagova).
Pred kraj uvodnog članka prikazaćemo i ostale načine za pristup elementima DOM stabla.
Pristup DOM elementima preko CSS selektora (querySelector i querySelectorAll)
Preko funkcije querySelectorAll
....
.... mogu se pronaći svi elementi unutar DOM stabla na koje se odnosi selektor koji je (u obliku niske), predat kao argument.
Međutim, budući da može postojati više elemenata na koje se određeni selektor odnosi, pristup elementima nije jednostavan kao u slučaju kada se (samo jedan) element traži preko id-a.
Funkcija getElementById
vraća jedan objekat (sada znamo zašto je veoma bitno da id bude jedinstven (!)), dok naredba querySelectorAll
vraća listu elemenata koji odgovaraju kriterijumu pretrage, i stoga se mora koristiti i drugačiji mehanizam za pristup pojedinačnim elementima:
Objekat slike
je referenca na listu pronađenih elemenata (koji odgovaraju prethodno navedenom CSS selektoru), pri čemu se lista kreira bez obzira na to da li elementi na koje se selektor odnosi, postoje, ili ne postoje:
- kada nema elemenata za koje selektor važi, lista je prazna
- kada postoji samo jedan element za koji selektor važi, lista sadrži jedan element (neće biti vraćen 'običan' pojedinačni objekat)
- kada postoji više elemenata za koje selektor važi, lista sadrži više elemenata
Ukoliko smo sigurni da postoji samo jedan element koji može biti pronađen, elementu se može pristupiti na jednostavniji način:
Funkcija querySelector
vraća objekat (ovoga puta nije u pitanju lista), a čak i ako se pređemo sa ovakvim pristupom, neće biti "strašno":
- ukoliko ne postoji nijedan element koji odgovara navedenom selektoru, objekat
slika
imaće vrednostnull
- ukoliko postoji više elemenata koji odgovaraju selektoru, biće izabran prvi
.... ali, na nama je da pazimo da ne dođemo u situaciju #2 ako nam trebaju svi elementi, a ne samo jedan, tj. prvi element (drugim rečima: moramo biti sigurni da pozivamo selektor koji se odnosi na jedinstveni objekat :)).
Pristup DOM elementima preko pripisane klase (getElementsByClass)
Za pristup elementima preko pripisane klase, može se koristiti funkcija getElementsByClass
. *
Sledeći kod:
.... pronalazi sve elemente kojima je pripisana klasa nav_linkovi
, i potom kreira listu (koja se takođe može obraditi onako kako smo videli u prethodnom odeljku).
Pristup DOM elementima preko opštih tagova (getElementsByTagName)
Pored metoda koje smo do sada prikazali, elementima se može pristupati i preko opštih odrednica za tagove, preko naredbe getElementsByTagName
(ali - i u ovom slučaju se isti efekat može postići korišćenjem naredbe querySelectorAll
).
Sledeći kod:
.... pronalazi sve h2
naslove unutar dokumenta, i potom (i ovoga puta), kreira listu kojoj se može pristupati na način koji je ranije opisan.
Sledeći koraci ....
Opcije koje smo prikazali u članku, predstavljaju (kao što možete pretpostaviti), samo mali deo onog što JavaScript nudi, pogotovo posle nekoliko najnovijih revizija jezika.
U člancima koje pripremamo za blisku budućnost, pisaćemo o okruženju Node JS, o ES6 sintaksi, kao i o mnogim drugim temama koje su vezane za JavaScript.
Do tada: ....