http://mbranko.github.io/webkurs
Ovo je deo web kursa
Mogućnost da pozovemo više metoda prosleđujući im isti objekat:
str.replace("k", "R").toUpperCase().substr(0,4);
var userController = {
currentUser: "",
findUser: function (userEmail) {
var arrayLength = usersData.length, i;
for (i = arrayLength - 1; i >= 0; i--) {
if (usersData[i].email === userEmail) {
this.currentUser = usersData[i];
break;
}
}
return this;
},
formatName: function () {
if (this.currentUser) {
this.currentUser.fullName = this.currentUser.firstName + " " + this.currentUser.lastName;
}
return this;
},
createLayout: function () {
if (this.currentUser) {
this.currentUser.viewData = "Member: " + this.currentUser.fullName + "
"
+ "ID: " + this.currentUser.id + "
" + "Email: " + this.currentUser.email + "
";
}
return this;
},
};
Svaka funkcija vraća this
.
Primer pozivanja
userController.findUser("test2@test2.com").formatName().createLayout().displayUser();
$("#btn_1").click(function() {
alert("Btn 1 Clicked");
});
var friends = ["Mike", "Stacy", "Andy", "Rick"];
friends.forEach(function (eachName, index){
console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick
});
primer imenovane callback funkcije
var allUserData = [];
function logStuff(userData) {
if (typeof userData === "string")
console.log(userData);
else if (typeof userData === "object")
for (var item in userData)
console.log(item + ": " + userData[item]);
}
function getInput(options, callback) {
allUserData.push(options);
callback(options);
}
getInput ({name:"Rich", speciality:"JavaScript"}, logStuff);
Callback funkcija prima parametar od funkcije koja je obuhvata.
callback može pristupiti globalnim promenljivima
var defaultOptions = { ... };
function logStuff(userData) {
if (!userData)
userData = defaultOptions;
...
}
pre poziva može se proveriti da li je u pitanju funkcija
function getInput(options, callback) {
allUserData.push(options);
if (typeof callback === "function")
callback(options);
}
Kada je callback metoda koja koristi this
:
// definiši objekat sa metodom
// metodu ćemo kasnije proslediti kao callback
var clientData = {
id: 094545,
fullName: "Not Set",
setUserName: function (firstName, lastName) {
this.fullName = firstName + " " + lastName;
}
}
function getUserInput(firstName, lastName, callback) {
callback (firstName, lastName);
}
Kada se pozove setUserName
, this
se ne odnosi na objekat clientData
nego na window
objekat u web čitaču, jer je getUserInput
globalna funkcija. U globalnoj funkciji this
pokazuje na window
.
Funkcije call
i apply
mogu da postave this
unutar funkcije i proslede parametre funkciji. Obe funkcije primaju novu vrednost za this
kao prvi parametar.
// dodali smo novi parametar ovde: callbackObj
function getUserInput(firstName, lastName, callback, callbackObj) {
callback.apply(callbackObj, [firstName, lastName]);
}
Drugi parametar za apply
je niz koji će se proslediti funkciji kao njeni parametri.
Naredni parametri za call
su parametri koji će se direktno proslediti funkciji.
Možemo proslediti više callback funkcija odjednom prilikom poziva.
function successCallback() { ... }
function completeCallback() { ... }
function errorCallback() { ... }
$.ajax({
url: "http://fiddle.jshell.net/favicon.png",
success: successCallback,
complete: completeCallback,
error: errorCallback
});
Primer koristi jQuery ajax
funkciju.
Callback poziva callback poziva callback poziva...
var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
p_client.dropDatabase(function(err, done) {
p_client.createCollection('test_custom_key', function(err, collection) {
collection.insert({'a':1}, function(err, docs) {
collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
cursor.toArray(function(err, items) {
test.assertEquals(1, items.length);
// Let's close the db
p_client.close();
});
});
});
});
});
});
Dva pristupa rešavanju ovog problema:
Funkcija je jedina stvar u JavaScriptu koja pravi novi opseg. Pogledajmo primer...
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
function dayName(number) {
return names[number];
}
console.log(dayName(1)); // → Monday
... kada se malo preradi:
var dayName = function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return function(number) {
return names[number];
};
}();
console.log(dayName(3)); // → Wednesday
names
je sada lokalna promenljiva. Ova funkcija se kreira i odmah poziva, a njen rezultat je funkcija koja se smešta u promenljivu dayName
. Ovde može biti hiljade linija koda sa puno lokalnih promenljivih; one bi bile vidljive samo u našoj funkciji ali ne i spolja.
Sada hoćemo da vratimo dve funkcije! Moramo ih spakovati u objekat:
var weekDay = function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
}();
console.log(weekDay.name(weekDay.number("Sunday"))); // → Sunday
Za veće module, sakupljanje svih vrednosti u objekat na kraju funkcije može biti nečitko. Želimo da eksportovane funkcije definišemo na zgodnijem mestu.
Deklarišemo objekat i dodajemo osobine u njega kad god imamo nešto za eksport.
(function(exports) {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
})(this.weekDay = {});
console.log(weekDay.name(weekDay.number("Saturday"))); // → Saturday
Samo jedna promenljiva u globalnom opsegu - weekDay
. Ali šta ako
dva modula koriste isto ime za globalnu promenljivu?
Deklarišemo objekat i dodajemo osobine u njega kad god imamo nešto za eksport.
(function(exports) {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
})(this.weekDay = {});
console.log(weekDay.name(weekDay.number("Saturday"))); // → Saturday
Samo jedna promenljiva u globalnom opsegu - weekDay
. Ali šta ako
dva modula koriste isto ime za globalnu promenljivu?
require
koja će, za dato ime modula, da ga učita iz fajla ili sa weba i vratiti odgovarajuću vrednostrequire
Prost način: upotreba eval
.
function evalAndReturnX(code) {
eval(code);
return x;
}
console.log(evalAndReturnX("var x = 2")); // → 2
Za nevolju, eval
će rezultate izvršavanja upisati u tekući opseg.
Bolji način: pomoću Function
konstruktora. On prima dva argumenta: string sa nazivima parametara funkcije razdvojenih zarezima i string sa telom funkcije.
var plusOne = new Function("n", "return n + 1;");
console.log(plusOne(4)); // → 5
Pri kraju smo: umotaćemo kod modula u funkciju, i ta funkcija postaje opseg za naš modul.
Minimalna implementacija za require
:
function require(name) {
var code = new Function("exports", readFile(name));
var exports = {};
code(exports);
return exports;
}
console.log(require("weekDay").name(1)); // → Monday
Pošto funkcija require
umotava kod u funkciju, nema potrebe da to radimo u modulu.
Sada fajl sa modulom može ovako da izgleda:
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
Tipičan modul će na vrhu učitati module koji su mu potrebni:
var weekDay = require("weekDay");
var today = require("today");
console.log(weekDay.name(today.dayNumber()));
require
će izvršiti kod modula svaki put kad se učitava.exports
objekta (npr. funkciju).Moduli će dobiti promenljivu module
koja je objekat sa osobinom exports
. Ova osobina inicijalno pokazuje na {}
koji je kreirao require
.
function require(name) {
if (name in require.cache)
return require.cache[name];
var code = new Function("exports, module", readFile(name));
var exports = {}, module = {exports: exports};
code(exports, module);
require.cache[name] = module.exports;
return module.exports;
}
require.cache = Object.create(null);
define(["weekDay", "today"], function(weekDay, today) {
console.log(weekDay.name(today.dayNumber()));
});
Funkcija define
prima niz sa nazivima modula i zatim funkciju koja prima po jedan parametar za svaki modul. Učitaće module u pozadini dok stranica radi. Kada su svi moduli učitani pozvaće datu funkciju koja će obaviti inicijalizaciju.
Nova verzija našeg modula:
define([], function() {
var names = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
});
Funkcija define
prima niz sa nazivima modula i zatim funkciju koja prima po jedan parametar za svaki modul. Učitaće module u pozadini dok stranica radi. Kada su svi moduli učitani pozvaće datu funkciju koja će obaviti inicijalizaciju.
Funkcija getModule
će učitati modul ili ga izvući iz keša.
var defineCache = Object.create(null);
var currentMod = null;
function getModule(name) {
if (name in defineCache)
return defineCache[name];
var module = {exports: null,
loaded: false,
onLoad: []};
defineCache[name] = module;
backgroundReadFile(name, function(code) {
currentMod = module;
new Function("", code)();
});
return module;
}
Funkcija backgroundReadFile
nije jednostavna.
Funkcija define
će pozvati inicijalizaciju modula kada prikupi sve potrebne module.
function define(depNames, moduleFunction) {
var myMod = currentMod;
var deps = depNames.map(getModule);
deps.forEach(function(mod) {
if (!mod.loaded)
mod.onLoad.push(whenDepsLoaded);
});
function whenDepsLoaded() {
if (!deps.every(function(m) { return m.loaded; }))
return;
var args = deps.map(function(m) { return m.exports; });
var exports = moduleFunction.apply(null, args);
if (myMod) {
myMod.exports = exports;
myMod.loaded = true;
myMod.onLoad.forEach(function(f) { f(); });
}
}
whenDepsLoaded();
}
Funkcija define
će učitati modul ili ga izvući iz keša pomoću getModule
. Njen zadatak je da se moduleFunction
(funkcija koja sadrži kod modula) pozove kada su učitani svi potrebni moduli. Zato definiše whenDepsLoaded
koja se dodaje na kraj onLoad
niza svih trenutno nedostajućih modula. Ova funkcija se odmah vraća ako ima još neučitanih modula. Tako će se posao obaviti samo jednom, kada se učita i poslednji modul. Poziva se i direktno iz define
ako tekući modul nema potrebnih modula.
Kada su svi potrebni moduli dostupni, whenDepsLoaded
poziva funkciju koja obmotava modul dajući joj sve tražene module kao parametre.
RequireJS radi na ovaj način.
Implementirani u posebnim bibliotekama od ranije, npr:
Podrška u web čitačima:
Ili ovaj polyfill za starije čitače.
Uvedeni u ECMAScript 6.
Promise je proxy za vrednost koju ne moramo znati u vreme kreiranja. Može biti u tri stanja:
var img1 = document.querySelector('.img-1');
img1.addEventListener('load', function() {
// ...
});
img1.addEventListener('error', function() {
// ...
});
Događaj može da se desi pre nego što počnemo da ga osluškujemo!
Da probamo da iskoristimo osobinu complete
za slike:
var img1 = document.querySelector('.img-1');
function loaded() {
// ...
}
if (img1.complete) {
loaded();
}
else {
img1.addEventListener('load', loaded);
}
img1.addEventListener('error', function() {
// ...
});
Ne hvata slike koje su proizvele grešku pre nego što smo počeli da slušamo.
Ako treba da obradimo više slika...
Idealno nam treba nešto ovakvo:
img1.callThisIfLoadedOrWhenLoaded(function() {
// loaded
}).orIfFailedCallThis(function() {
// failed
});
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
// all loaded
}).orIfSomeFailedCallThis(function() {
// one or more failed
});
Ovaj problem rešavaju promise objekti.
Kada bi img
element imao ready
metodu koja vraća promise:
img1.ready().then(function() {
// loaded
}, function() {
// failed
});
Promise.all([img1.ready(), img2.ready()]).then(function() {
// all loaded
}, function() {
// one or more failed
});
Promise - slično osluškivanju događaja osim:
Ovako se kreira promise:
var promise = new Promise(function(resolve, reject) {
// uradi neki posao
if (/* sve je u redu? */) {
resolve("Radi!");
} else {
reject(Error("Ne radi!"));
}
});
A ovako se koristi:
promise.then(function(result) {
console.log(result); // "Radi!"
}, function(err) {
console.log(err); // Error: "Ne radi!"
});
then
prima dva parametra
Naša stranica bi trebalo da:
U slučaju greške treba obavestiti korisnika i zaustaviti spinner.
function get(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
if (req.status == 200)
// uspešan ishod
resolve(req.response);
else
// neuspešan ishod
reject(Error(req.statusText));
};
// mrežne greške
req.onerror = function() {
reject(Error("Network Error"));
};
// pošalji zahtev
req.send();
});
}
Korišćenje prethodno napravljenog promise:
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.error("Failed!", error);
})
Rezultat učitavanja će biti JSON tekst koji treba parsirati:
get('story.json').then(function(response) {
return JSON.parse(response);
}).then(function(response) {
console.log("Yey JSON!", response);
})
Pošto JSON.parse
prima jedan parametar, to može i kraće:
get('story.json').then(JSON.parse).then(function(response) {
console.log("Yey JSON!", response);
})
Ako callback vrati
then
će je preuzetithen
će je sačekati
getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
console.log("Got chapter 1!", chapter1);
})
then
prima dva parametra:
get('story.json').then(function(response) {
console.log("Success!", response);
}, function(error) {
console.log("Failed!", error);
})
Može i pomoću catch
:
get('story.json').then(function(response) {
console.log("Success!", response);
}).catch(function(error) {
console.log("Failed!", error);
})
Ali to nije baš isto!
Kada se koristi catch
:
get('story.json').then(function(response) {
console.log("Success!", response);
}).catch(function(error) {
console.log("Failed!", error);
})
je ekvivalentno sa:
get('story.json').then(function(response) {
console.log("Success!", response);
}).then(undefined, function(error) {
console.log("Failed!", error);
})
Sa then(f1, f2)
, biće pozvana ili f1
ili f2
, nikada obe.
Sa then(f1).catch(f2)
, biće pozvane obe i ako f1
proizvede grešku jer su to posebni koraci u lancu.
Negativan ishod za promise dobija se
reject
callback-a
var jsonPromise = new Promise(function(resolve, reject) {
// JSON.parse baca grešku ako tekst nije pravilan JSON
// tako da se ovo implicitno reject-uje
resolve(JSON.parse("This ain't JSON"));
});
jsonPromise.then(function(data) {
// nikad se neće desiti
console.log("It worked!", data);
}).catch(function(err) {
// ovo će se desiti
console.log("It failed!", err);
})
Isto važi i za greške koje nastanu u callbacku za then
:
get('/').then(JSON.parse).then(function() {
// Ovo se neće desiti, '/' je HTML strana, ne JSON
// pa će JSON.parse baciti izuzetak
console.log("It worked!", data);
}).catch(function(err) {
// ovo će se desiti
console.log("It failed!", err);
})
U primeru sa učitavanjem knjige:
getJSON('book.json').then(function(book) {
return getJSON(book.chapterUrls[0]);
}).then(function(chapter1) {
addHtmlToPage(chapter1.html);
}).catch(function() {
addTextToPage("Failed to show chapter");
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Ako getJSON(book.chapterUrls[0])
vrati grešku, preskaču sve svi then
callbacks, i prelazi na catch
callback. Spinner će se isključiti u oba slučaja.
Prethodni primer je asinhrona varijanta sledeće ideje:
try {
var story = getJSONSync('story.json');
var chapter1 = getJSONSync(story.chapterUrls[0]);
addHtmlToPage(chapter1.html);
} catch (e) {
addTextToPage("Failed to show chapter");
}
document.querySelector('.spinner').style.display = 'none'
Ako koristimo catch
samo da zabeležimo grešku ali nastavljamo rad:
function getJSON(url) {
return get(url).then(JSON.parse).catch(function(err) {
console.log("getJSON failed for", url, err);
throw err;
});
}
Ponovo bacimo grešku.
Počnimo od sinhrone varijante koja učitava sva poglavlja:
try {
var story = getJSONSync('story.json');
addHtmlToPage(story.heading);
story.chapterUrls.forEach(function(chapterUrl) {
var chapter = getJSONSync(chapterUrl);
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}
catch (err) {
addTextToPage("Argh, broken: " + err.message);
}
document.querySelector('.spinner').style.display = 'none'
Sinhrono izvršavanje će blokirati web čitač dok traje download.
Počnimo od sinhrone varijante koja učitava sva poglavlja:
try {
var story = getJSONSync('story.json');
addHtmlToPage(story.heading);
story.chapterUrls.forEach(function(chapterUrl) {
var chapter = getJSONSync(chapterUrl);
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}
catch (err) {
addTextToPage("Argh, broken: " + err.message);
}
document.querySelector('.spinner').style.display = 'none'
Sinhrono izvršavanje će blokirati web čitač dok traje download.
Asinhrona varijanta bi trebalo da izgleda ovako:
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// TODO: za svaki url u story.chapterUrls, dobavi i prikaži ga
}).then(function() {
// završili smo
addTextToPage("All done");
}).catch(function(err) {
// uhvati usputne greške
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// uvek
document.querySelector('.spinner').style.display = 'none';
})
Ali kako da prođemo kroz poglavlja i učitavamo ih u pravom redosledu? Ovo neće raditi:
story.chapterUrls.forEach(function(chapterUrl) {
// dobavi poglavlje
getJSON(chapterUrl).then(function(chapter) {
// dodaj ga na stranicu
addHtmlToPage(chapter.html);
});
})
forEach
nije async-aware, tj. ne vodi računa o završetku operacije za svaki element sekvence. Poglavlja će se pojavljivati redosledu dobavljanja umesto u pravom redosledu.
Treba da pretvorimo chapterUrls
u listu promisa.
// počni od promisa koji je uvek uspešan
var sequence = Promise.resolve();
// iteracija kroz chapterUrls
story.chapterUrls.forEach(function(chapterUrl) {
// dodaj ove akcije na kraj sekvence
sequence = sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
})
Ovo može i pomoću Array.reduce
:
// iteracija kroz chapterUrls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
// dodaj ove akcije na kraj sekvence
return sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve())
Ne treba nam posebna promenljiva.
Rešenje:
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
return story.chapterUrls.reduce(function(sequence, chapterUrl) {
// kada je završio promise prethodnog poglavlja
return sequence.then(function() {
// dobavi sledeće poglavlje
return getJSON(chapterUrl);
}).then(function(chapter) {
// i dodaj ga na stranicu
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
// sve je gotovo
addTextToPage("All done");
}).catch(function(err) {
// uhvati greške usput
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
// uvek skloni spinner na kraju
document.querySelector('.spinner').style.display = 'none';
})
Zašto da radimo download sekvencijalno? Postoji API:
Promise.all(arrayOfPromises).then(function(arrayOfResults) {
//...
})
Promise.all
prima niz promisa i kreira promise koji je ispunjen kada se svi uspešno završe. Dobija se niz rezultata u redosledu koji odgovara redosledu promisa.
Promise.all
primenjeno na naš problem:
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// kreiraj niz promisa i čekaj na sve njih
return Promise.all(
// mapiraj chapterUrls niz na niz JSON promisa
story.chapterUrls.map(getJSON)
);
}).then(function(chapters) {
// sada imamo JSON-e u pravom redosledu, iteriramo kroz njih
chapters.forEach(function(chapter) {
// i dodajemo u stranicu
addHtmlToPage(chapter.html);
});
addTextToPage("All done");
}).catch(function(err) {
// uhvati greške usput
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Kada stigne poglavlje 1 možemo ga dodati na stranicu.
Kada stigne poglavlje 3 ne možemo ga dodati na stranicu jer nije još stiglo poglavlje 2.
Kada stigne poglavlje 2 možemo dodati poglavlja 2 i 3 na stranicu.
Dobavićemo sva poglavlja paralelno, ali ćemo kreirati sekvencu za dodavanje u stranicu.
Paralelni download, sekvencijalno dodavanje u stranicu:
getJSON('story.json').then(function(story) {
addHtmlToPage(story.heading);
// Mapiraj niz chapterUrls na niz JSON promisa. Oni će se dobavljati paralelno.
return story.chapterUrls.map(getJSON)
.reduce(function(sequence, chapterPromise) {
// pomoću reduce ćemo ulančati promise, dodajući sadržaj na stranicu za svako poglavlje
return sequence.then(function() {
// sačekaj na sve u sekvenci, onda sačekaj na poglavlje
return chapterPromise;
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
}).then(function() {
addTextToPage("All done");
}).catch(function(err) {
addTextToPage("Argh, broken: " + err.message);
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
})
Forma za unos podataka
<section>
<form onsubmit="javascript:setSettings()">
<label>Select your BG color: </label>
<input id="favcolor" type="color" value="#ffffff" />
<label>Select Font Size: </label>
<input id="fontwt" type="number" max="14" min="10" value="13" />
<input type="submit" value="Save" />
<input onclick="clearSettings()" type="reset" value="Clear" />
</form>
</section>
Test podrške za local storage:
function setSettings() {
if ('localStorage' in window && window['localStorage'] !== null) {
// postoji podrška za local storage
} else {
alert('Nije moguće sačuvati podešavanja jer Vaš čitač nema local storage');
}
}
Čitkije pomoću Modernizr:
<script type="text/javascript" src="modernizr.min.js"></script>
if (Modernizr.localstorage) {
// postoji podrška za local storage
} else {
alert('Nije moguće sačuvati podešavanja jer Vaš čitač nema local storage');
}
Dodat kod za čuvanje podataka:
function setSettings() {
if (Modernizr.localstorage) {
try {
var favcolor = document.getElementById('favcolor').value;
var fontwt = document.getElementById('fontwt').value;
localStorage.setItem('bgcolor', favcolor);
localStorage.fontweight = fontwt;
} catch (e) {
if (e == QUOTA_EXCEEDED_ERR) { // premašio 10 MB
alert('Quota exceeded!');
}
}
} else {
alert('Nije moguće sačuvati podešavanja jer Vaš čitač nema local storage');
}
}
Čitanje iz local storage:
function applySettings() {
if (localStorage.length != 0) {
document.body.style.backgroundColor = localStorage.getItem('bgcolor');
document.body.style.fontSize = localStorage.fontweight + 'px';
document.getElementById('favcolor').value = localStorage.bgcolor;
document.getElementById('fontwt').value = localStorage.fontweight;
} else {
document.body.style.backgroundColor = '#FFFFFF';
document.body.style.fontSize = '13px'
document.getElementById('favcolor').value = '#FFFFFF';
document.getElementById('fontwt').value = '13';
}
}
length
vraća broj sačuvanih elemenata.
Uklanjanje iz local storage:
function clearSettings() {
localStorage.removeItem("bgcolor");
localStorage.removeItem("fontweight");
document.body.style.backgroundColor = '#FFFFFF';
document.body.style.fontSize = '13px'
document.getElementById('favcolor').value = '#FFFFFF';
document.getElementById('fontwt').value = '13';
}
Prilikom upisa ili brisanja, poseban događaj će se desiti za window
objekat. Možemo dodati osluškivanje za taj događaj.
window.addEventListener('storage', storageEventHandler, false);
function storageEventHandler(event) {
applySettings();
}
Event ima sledeće atribute:
localStorage
ili sessionStorage
objekatDogađaj će se desiti samo u drugim prozorima - ne i u prozoru koji ga je izazvao. Desiće se samo ako je došlo do promene u podacima.
Kako web čitači učitavaju sadržaj:
Skript je zgodno staviti na sam kraj HTML fajla, neposredno ispred </body>
taga. To garantuje da će čitač parsirati ceo fajl i da je DOM spreman pre nego što mu skript pristupi.
<!DOCTYPE html>
<html>
<body>
<p id="myParagraph">This is my paragraph!
<span class="hideme">Lorem ipsum</span>
dolor sit amet.
</p>
<p class="hideme">Another paragraph!</p>
<script>
var myPar = document.getElementById("myParagraph");
myPar.innerText = "I have changed the content!";
</script>
</body>
</html>
Ako stavimo skript na početak body
počeće da se izvršava pre nego što je dokument u celosti parsiran što može dovesti do greške.
<!DOCTYPE html>
<html>
<body>
<script>
var myPar = document.getElementById("myParagraph");
myPar.innerText = "I have changed the content!";
</script>
<p id="myParagraph">This is my paragraph!
<span class="hideme">Lorem ipsum</span>
dolor sit amet.
</p>
</p>
<p class="hideme">Another paragraph!</p>
</body>
</html>
Šta ako imamo jednostavan dokument koga puno menja skript?
var defaultManifest = [
"scripts/js-lib.js",
"scripts/js-objects.js",
"scripts/third-party/omniture.js",
"http://big.cdn.com/useful-library.js"
]
function loadManifest(arrManifest) {
var i, arrManifestLength = arrManifest.length;
for (i = 0; i < arrManifestLength; i++) {
var newScript = document.createElement("script");
newScript.src = arrManifest[i];
document.getElementsByTagName("head")[0].appendChild(newScript);
}
}
Programski kreiramo script
tag unutar head
.
<!DOCTYPE html>
<html>
<head>
<script src="scripts/js-loader.js"></script>
<script>
loadManifest(defaultManifest);
</script>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Sada se skriptovi učitavaju nakon što se programski kreiraju script
čvorovi ali to ne blokira učitavanje stranice i drugih elemenata.
Šta ako imamo jednostavan dokument koga puno menja skript?
Iz bezbednosnih razloga skriptovi su ograničeni samo na pristup podacima koji imaju isto poreklo (origin). Poreklo je definisano u RFC 6454 kao kombinacija
Primer: http://www.example.com/dir/page.html
URL | status | razlog |
---|---|---|
http://www.example.com/dir/page2.html | OK | isti protokol, host i port |
http://www.example.com/dir2/other.html | OK | isti protokol, host i port |
http://username:password@www.example.com/dir2/other.html | OK | isti protokol, host i port |
http://www.example.com:81/dir/other.html | NOK | različit port |
https://www.example.com/dir/other.html | NOK | različit protokol |
http://en.example.com/dir/other.html | NOK | različit host |
http://example.com/dir/other.html | NOK | različit host |
http://v2.www.example.com/dir/other.html | NOK | različit host |
http://www.example.com:80/dir/other.html | ??? | zavisi od browsera |
CORS (Cross-Origin Resource Sharing) pruža mogućnost da sajt A dopusti pristup svojim podacima skriptovima sa sajta B.
Server A treba da doda CORS zaglavlja u svoj HTTP odgovor.
Podrška za CORS u web čitačima:
Tekuće stanje je na http://caniuse.com/#search=cors.
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) { // da li je XMLHttpRequest2 objekat
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// XDomainRequest: specijalan slučaj za MSIE
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null; // CORS nije podržan
}
return xhr;
}
var xhr = createCORSRequest('GET', url);
if (!xhr) {
throw new Error('CORS not supported');
}
Treba definisati callback za onload
i onerror
.
xhr.onload = function() {
var responseText = xhr.responseText;
console.log(responseText);
// obradi odgovor
};
xhr.onerror = function() {
console.log('Greška!');
};
Standardni CORS zahtevi ne podrazumevaju cookies. Za uključivanje cookies treba:
xhr.withCredentials = true;
Server sa svoje strane mora da omogući credentials odgovarajućim zaglavljem:
Access-Control-Allow-Credentials: true
Ovi cookies neće biti dostupni u JavaScriptu zbog SOP.
Kada je sve pripremljeno:
xhr.send();
Ako zahtev ima telo, ono se prosleđuje kao parametar send
.
Najveći posao će obaviti web čitač i server. Čitač će dodati zaglavlja i po potrebi slati dodatne zahteve.
Prosti CORS zahtevi imaju:
var url = 'http://api.alice.com/cors';
var xhr = createCORSRequest('GET', url);
xhr.send();
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Origin
zaglavlje je dodao čitač i ne može se uticati na njega!
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
...
*
(za sve domene)Složeni zahtevi su potrebni ako šaljemo druge metode kao PUT i DELETE ili ako prenosimo JSON podatke.
var url = 'http://api.alice.com/cors';
var xhr = createCORSRequest('PUT', url);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
Čitač prvo šalje probni zahtev (OPTIONS) da proveri da li ima dozvolu da šalje pravi zahtev. Posle pozitivnog odgovora šalje pravi zahtev. Ovo se odvija transparentno za JavaScript program. Probni zahtev se može keširati da se ne bi slao više puta.
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
Ako se izostave CORS zaglavlja, signalizira se neispravan zahtev.