Uvod
Ajax je skraćenica od “Asinhroni JavaScript + XML” (iako se danas uglavnom koristi JSON), a predstavlja grupu tehnologija namenjenu za dinamičko kreiranje Web stranica. Korišćenjem AJAX-a poboljšavamo kvalitet interkativnosti sa korisnikom, uz želju da što više liči na desktop aplikacije (prema brzini interakcije). Ideja na kojoj se zasniva Ajax jeste da se stranica na kojoj se odvija Web aplikacija učita samo jednom, a da se svaka dalja komunikacija sa serverom izvršava asinhrono bez blokiranja interfejsa i bez ponovnog učitavanja čitave stranice. Asinhrono ponašanje podrazumeva da nakon interakcije korisnika sa interfejsom, zahtev ka serveru prihvata JavaScript i XMLHttpRequest objekat, koji u pozadini šalje zahteve serveru a prikazujući rezultate kada budu raspoloživi, dok korisnik u međuvremenu može da nastavi sa radom.
Ajax nije bez mana, budući da se Ajax stranice dinamički generišu najveći problem za sajtove je optimizacija za pretraživače. Pretraživači često ne mogu dobro da protumače sajt, što dovodi do problema u indeksiranju sajtova. Sličan problem postoji i sa alatima za analizu posećenosti stranica, jer korisnik može ceo dan da provede na jednoj Ajax stranici, a klasični alati za analizu posećenosti će to protumačiti kao jedno prikazivanje stranice.
XMLHttpRequest objekat
XMLHttpRequest je temelj AJAX-a i predstavlja JavaScript objekat koji se koristi za slanje HTTP zahteva. Dizajniran od strane Microsofta a zatim prihvaćen i od ostalih velikih pretraživača i 2014-te postao standard u W3C.
Svojstva XHR objekta
Svojstvo | Opis |
---|---|
readyState | Numerička vrednost kojom se opisuje stanje objekta moguća stanja su: 0, 1, 2, 3 i 4
|
responsType | Tekstualni podatak koji odgovara tipu serverskog odgovora moguće vrednosti su: “”, “arraybuffer”, “blob”, “document”, “json” i “text” |
response | Svojstvo kojim se pristupa telu serverskog odgovora |
responseText | Vraća odgovor servera kao string |
responseXML | Vraća odgovor servera kao XML data |
status | Numerička vrednost koja odgovara HTTP satusu serverskog odgovora. Najviše nas interesuje status 200 – indikacija da je zahtev obradjen uspešno |
statusText | Tekstualna poruka koja odgovara HTTP poruci serverskog odgovora (npr. “Not Found” or “OK”) |
timeout | Numerička vrednost nakon koje će HTTP zahtev završiti; trebalo bi da timeout vrednost bude nešto veća nego vreme predviđeno za generisanje odgovora; vrednost responseText svojstva će u tom slučaju biti null |
withCredentials | Boolean-ova vrednost koja definiše da li je za cross-site Access-Control zahtev potrebni credentials kao što je cookies, autorizovani header ili TLS sertifikat. |
Metode XHR objekta
Method | Description |
---|---|
abort() | Otkazuje trenutni zatev |
getAllResponseHeaders() | Vraća sve informacije o header-u |
getResponseHeader() | Vraća specifične informacije o header-u |
open(method,url,async,uname,pswd) | Funkcija kojom se inicira HTTP zahtev a parametri koje prihvata su:
|
send() | Metoda kojom se šalju podaci na server (ako imamo šta da pošaljemo) |
setRequestHeader(header, value) | Pri slanju podataka dodaje label/value par u header-u. Mora da se pozove posle open() a pre send() metode |
Postupak
a) Kreiranje objekta
Kreira se pozivanjem konstruktora XMLHttpRequest:
1 |
var xhr = new XMLHttpRequest(); |
b) Event handler i callback funkcija
Dogadjaji (events) vezani za XHR objekat
Dogadjaji vezani za XHR objekat mogu biti:
Tip | Opis | Broj pojavljivanja | Kada | |||
---|---|---|---|---|---|---|
onreadystatechange | Ovaj način je najstariji i podržan je u svim browserima, kod njega eventHandler prati svaku promenu stanja objekta (readyState) i “okida” pri svakoj promeni stanja.
|
|||||
loadstart | Proces je započet. | Jednom | Prvo | |||
progress | Proces je u toku | nula ili više | Kada je loadstart završen. | |||
error | Sa ovim eventListener-om osluškujemo network-level error-e:
|
|||||
abort | Proces je prekinut. | Jednom ili nijednom | ||||
load | onreadystatechange “okida” dogadjaj pri svakoj promeni stanja objekta, medjutim nas najčešće interesuje samo kada se stanje objekta promeni na vrednost: readyState == 4. Ukoliko vam nisu od značaja informacije o drugim stanjima i želite da smanjite “okidanje” nepotrebnih dogadjaja možda je bolji noviji i “moderniji” pristup kada se generiše samo jedan dogadjaj “load” kada server odgovori:
|
|||||
loadend | Ceo proces je završen | Jednom | Aktivira se posle neke greške, namernog prekida (abort) ili kada je load završen uspešno. |
c) Definisanje parametara konekcije
Nakon kreiranja XMLHttpRequest objekta, potrebno je definisati osnovne parametre za komunikaciju. Ova metoda ne šalje zahtev web server-u, već čuva svoje argumente za kasnije slanje request-a.
1 |
xhr.open(method,url,async,username,password) |
Opis argumenata metode:
-
Metod
Metode koje mogu da se koriste su: POST, GET, HEADER, PUT, DELETE.
GET metoda šalje zahtev i podatke kroz url, kod nje se request se može keširati, zahtev ostaje u istoriji pretraživača i može se bookmark-ovati. Mana je ograničena veličina podataka koja se može proslediti kroz URL, kao i vidljiost podataka u URL-u (čitaj “nebezbednost“). Zbog čega se ova metoda i ne koristi za slanje važnih podataka.POST metode ne šalje podatke kroz URL već kroz atribut send() metode, pa je bezbednija i koristi se kada treba slati poverljive podatke (najčešće koje unosi korisnik). POST metoda se koristi i onda kada treba poslati veću količnu podataka s obizrom da nema ograničenu veličinu podatka kao što je to slučaj kod GET metode. Medjutim kod ove metode zahtev se nemože keširati, ne ostaje u istoriji browser-a pa se ne može ni bookmark-ovati.
HEAD metoda traži od servera header-e sa navedene URL adrese bez sadržaja dokumenta (koristi se npr. da bi se proverio datum izmene resursa). -
URL
ULR je url servera kojem se šalje zahtev
-
Asinhronost zahteva
Ovim parametrom se definiše asinhronost zahteva. Može biti true ili false. Ukoliko se izostavi ili definiše kao TRUE onda je zahtev asinroni.
NAPOMENA:
XMLHttpRequest zahtev može biti i sinhroni ali se to ne preporučuje jer ukoliko sever ne pošalje odgovor, metoda send() može da zablokira rad browser-a. Ne postoji “magično dugme” koje zaustavlja XMLHttpRequest, kao ni maksimalno vreme čekanja pa JavaScript engine ne dozvoljava zaustavljanje izvršenja zahteva kad je jednom poslat. -
User Name i Password
Korisničko ime i šifra su opcioni argumenti koji se navode ukoliko su potrebni za pristup serveru
d) Definisanje podataka zaglavlja (header-a)
Sintaksa
Sa metodom “setRequestHeader()” možemo da definišemo neke vrednosti u zaglavlju zahteva. Najčešće se koristi pri slanju podataka na server sa POST metodom. Parametar metode je u vidu “key, value” para.
1 |
xhr.setRequestHeader(key, value); |
NAPOMENA:
Metoda “setRequestHeader()” mora da bude smeštena izmedju open() i send() metode.
Primena
Najčešće se kroz parametar metode “setRequestHeader()” šalju podaci na koji način enkodovan poslati podatak.
1 |
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); |
Šta je enkodovanje?
Postoje tri načina na koji se može enkodovati:
Value | Description |
---|---|
application/x-www-form-urlencoded | Default-no ponašanje, svi znakovi se kodiraju pre slanja (razmaci se pretvaraju u “+” simbole, a specijalni znakovi se pretvaraju u ASCII HEX vrednosti. Ustvari podatak je query string koji se sastoji od parova ime/vrednostodvojenih sa znakom “&”, gde su imena odvojena od vrednosti znakom jednako “=”, ukoliko ima specijalnih znakova oni prebačeni u ASCII HEX vrednosti. |
multipart/form-data | Nijedan znak nije kodiran. Ova vrednost je potrebna kada koristite obrasce sa kontrolom za otpremanje datoteka |
text/plain | Prostori se pretvaraju u “+” simbole, ali ne postoje drugi posebni znaci |
Tip sadržaja “application/x-www-form-urlencoded” se koristi u većini slučajeva, osim za slanje velikih količina binarnih podataka ili teksta koji sadrže ne-ASCII znakove jer je za to neefikasan. A “multipart/form-data” treba koristiti za podnošenje formulara koji sadrže datoteke, ne-ASCII podatke i binarne podatke.
e) Pripema podataka za slanje
U slučaju slanja podataka potrebno je pripremiti podatke u obliku koji je pogodan za slanje.
Serijalizovanje podataka u “query string”
a) Jednostavan podatak
Ako je podatak jednostavan šalje se kao query string koji se sastoji od parova ime/vrednost odvojenih sa znakom “&”, gde su imena odvojena od vrednosti znakom jednako “=”, a ukoliko ima specijalnih znakova oni prebačeni u ASCII HEX vrednosti.
1 |
var data = "MyVariableOne=ValueOne&MyVariableTwo=ValueTwo"; |
b) Objekat
Ukoliko je u pitanju objekat/JSON potrebno je prvo ga prebaciti u string.
1 |
var arr = JSON.stringify([ 'foo', 'bar' ]); // Vraca: ["foo","bar"] |
Nakon čega enkodovati dobijeni string:
1 2 |
var encoArr = encodeURIComponent(arr) // Vraca: %5B%22foo%22%2C%22bar%22%5D var url = 'http://example.com/?data=' + encoArr ; |
Decodovanje i encodovanje možete proveriti preko online servisi kao što je https://www.freeformatter.com/url-encoder.html ili http://www.url-encode-decode.com/.
Primer
Ovaj snippet ima sličnu funkcionalnost kao jQuery metoda serialize()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function serialize(form) { var field, l, s = []; if (typeof form == 'object' && form.nodeName == "FORM") { var len = form.elements.length; for (var i=0; i<len; i++) { field = form.elements[i]; if (field.name && !field.disabled && field.type != 'file' && field.type != 'reset' && field.type != 'submit' && field.type != 'button') { if (field.type == 'select-multiple') { l = form.elements[i].options.length; for (var j=0; j<l; j++) { if(field.options[j].selected) s[s.length] = encodeURIComponent(field.name) + "=" + encodeURIComponent(field.options[j].value); } } else if ((field.type != 'checkbox' && field.type != 'radio') || field.checked) { s[s.length] = encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value); } } } } return s.join('&').replace(/%20/g, '+'); } |
Seriajalizovanje podataka u niz
Ovaj snippet ima sličnu funkcionalnost kao jQuery metoda serializeArray()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function serializeArray(form) { var field, l, s = []; if (typeof form == 'object' && form.nodeName == "FORM") { var len = form.elements.length; for (var i=0; i<len; i++) { field = form.elements[i]; if (field.name && !field.disabled && field.type != 'file' && field.type != 'reset' && field.type != 'submit' && field.type != 'button') { if (field.type == 'select-multiple') { l = form.elements[i].options.length; for (j=0; j<l; j++) { if(field.options[j].selected) s[s.length] = { name: field.name, value: field.options[j].value }; } } else if ((field.type != 'checkbox' && field.type != 'radio') || field.checked) { s[s.length] = { name: field.name, value: field.value }; } } } } return s; } |
f) Slanje zahteva
Iniciranje povezivanja pripremljenog i definisanog zahteva sa serverom se vrši sa metodom send(). Atribut metode send(atribut) možemo da koristim za prosledjivanje podataka serveru. Upravo kod POST metode je to glavni princip slanja podataka. Jednostavno sve “pripremljenje” podatke šaljemo kao atribut metode “send”. Madjutim pošto kod GET metode sve informacije šaljemo kroz URL, onda kod nje ne koristimo tu mogućnost slanja kroz atribut, već atribut definišemo sa null send(null) ili ga jednostavno ne popunimo.
g) Odgovor servera
Status zahteva
Statusu našeg zahteva kao odgovor servera se pristupa pomoću svojstva XMLHttpRequest objekta status
1 |
xhr.status |
a tekstu statusa sa:
1 |
xhr.statusText |
String odgovor
Odgovoru servera u vidu “string niza” se pristupa preko svojstva responseText
1 |
xhr.responseText |
NAPOMENA:
XMLHttpRequest nema direktno svojstvo za odgovor servera u JSON formatu, pa je zato potrebno parsirati odgovor:
JSON.parse(xhr.responseText);
XML odgovor
Odgovoru servera koji je neki XML se pristupa sa responseXML
1 |
xhr.responseXML |
HTTP zaglavlje serverskog odgovora
Pristup svim zaglavljima koje vraća web server je uz pomoć metode getResponseHeader() :
1 |
xhr.getResponseHeader() |
Primeri
OBJAŠNJENJE:
U primerima koristim online resurs myjson.com za brzo generisanje REST endpoint-a.
a) Dobijanje podataka sa servera
Primer br.1
JSON fajlu sačuvanog na sajtu http://myjson.com/ pristupam preko endpointa: https://api.myjson.com/bins/erxi9. Sa ajax request-om ka endpoint-u dobijam sledeći sadržaj:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[ { ime: "Pera", pozicija: "direktor" }, { ime: "Dragan", pozicija: "poslovodja" }, { ime: "Milan", pozicija: "radnik" }, { ime: "Zoran", pozicija: "radnik" }, { ime: "Dejan", pozicija: "radnik" } ] |
U ovome primeru se štampaju sve osobe koje zadovaoljavaju izabranu poziciju:
See the Pen Promise primer by Web programiranje (@chos) on CodePen.
Primer br.2
Za ovaj primer koristim generisani endpoint https://api.myjson.com/bins/amz5t.
See the Pen Ajax GET by Web programiranje (@chos) on CodePen.
b) Slanje podataka na server
Primer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var newName = 'John Smith'; var xhr = new XMLHttpRequest(); xhr.open('POST', 'myservice/username?id=some-unique-id'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = function() { if (xhr.status === 200 && xhr.responseText !== newName) { alert('Something went wrong. Name is now ' + xhr.responseText); } else if (xhr.status !== 200) { alert('Request failed. Returned status of ' + xhr.status); } }; xhr.send(encodeURI('name=' + newName)); |
Primer: slanje podataka iz forme
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
document.getElementById("myform").onsubmit = function(e) { e.preventDefault(); var f = e.target, formData = '', xhr = new XMLHttpRequest(); // fetch form values for (var i = 0, d, v; i < f.elements.length; i++) { d = f.elements[i]; if (d.name && d.value) { v = (d.type == "checkbox" || d.type == "radio" ? (d.checked ? d.value : '') : d.value); if (v) formData += d.name + "=" + escape(v) + "&"; } } xhr.open("POST", f.action); xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"); xhr.send(formData); } |
c) Slanje fajlova na server
1 2 3 4 5 |
var file = document.getElementById('test-input').files[0]; var xhr = new XMLHttpRequest(); xhr.open('POST', 'myserver/uploads'); xhr.setRequestHeader('Content-Type', file.type); xhr.send(file); |