Javascript JSON and Ajax v2 - 2
Javascript JSON and Ajax v2 - 2
Javascript JSON and Ajax v2 - 2
JSON o r XML?
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Introduction to JSON
Welco me to JavaScript 2! In this co urse, yo u'll learn advanced JavaScript, JSON and AJAX and ho w to use them to suit yo ur
pro fessio nal and creative go als.
Course Objectives
When yo u co mplete this co urse, yo u will be able to :
use JSON to serialize data fo r sto rage in the bro wser o r o n the server.
sto re and retrieve data using Ajax and Lo calSto rage.
o ptimize yo ur DOM manipulatio n co de with Do cument Fragments.
use Strings and Dates mo re effectively in yo ur co de.
catch erro rs with Exceptio ns.
add lo catio n and maps to yo ur applicatio ns with Geo lo catio n and Go o gle Maps.
mo dularize yo ur co de with Mo dernizr.
build a dynamic, interactive, fro nt-end web applicatio n.
Fro m beginning to end, yo u will learn by do ing yo ur o wn JavaScript-based pro jects using JSON and AJAX, and then handing
them in fo r instructo r feedback. These pro jects will result in an impressive final applicatio n which will add to yo ur po rtfo lio and
will co ntribute to certificate co mpletio n. Besides a bro wser and internet co nnectio n, all so ftware is pro vided o nline by the
O'Reilly Scho o l o f Techno lo gy.
Thro ugho ut the co urse, we'll build a To Do applicatio n that uses fo rm validatio n, lo cal sto rage, and Ajax. Ajax is a term used to
describe metho ds o f co mmunicating with reso urces external to yo ur JavaScript pro gram in o rder to send and retrieve data—
reso urces like a file, a database, o r a web service—as well as respo nding to user interactio ns and updating yo ur web
applicatio n dynamically. One o f the key co mpo nents o f Ajax is the data, so after a quick review o f o bjects and arrays, we'll take a
lo o k at ho w we can fo rmat data that we want to use in o ur web applicatio ns.
To learn a new skill o r techno lo gy, yo u have to experiment. The mo re yo u experiment, the mo re yo u learn. Our system
is designed to maximize experimentatio n and help yo u learn to learn a new skill.
We'll pro gram as much as po ssible to be sure that the principles sink in and stay with yo u.
Each time we discuss a new co ncept, yo u'll put it into co de and see what YOU can do with it. On o ccasio n we'll even
give yo u co de that do esn't wo rk, so yo u can see co mmo n mistakes and ho w to reco ver fro m them. Making mistakes
is actually ano ther go o d way to learn.
Abo ve all, we want to help yo u to learn to learn. We give yo u the to o ls to take co ntro l o f yo ur o wn learning experience.
When yo u co mplete an OST co urse, yo u kno w the subject matter, and yo u kno w ho w to expand yo ur kno wledge, so
yo u can handle changes like so ftware and o perating system updates.
T ype t he co de . Resist the temptatio n to cut and paste the example co de we give yo u. Typing the co de
actually gives yo u a feel fo r the pro gramming task. Then play aro und with the examples to find o ut what else
yo u can make them do , and to check yo ur understanding. It's highly unlikely yo u'll break anything by
experimentatio n. If yo u do break so mething, that's an indicatio n to us that we need to impro ve o ur system!
T ake yo ur t im e . Learning takes time. Rushing can have negative effects o n yo ur pro gress. Slo w do wn and
let yo ur brain abso rb the new info rmatio n tho ro ughly. Taking yo ur time helps to maintain a relaxed, po sitive
appro ach. It also gives yo u the chance to try new things and learn mo re than yo u o therwise wo uld if yo u
blew thro ugh all o f the co ursewo rk to o quickly.
Expe rim e nt . Wander fro m the path o ften and explo re the po ssibilities. We can't anticipate all o f yo ur
questio ns and ideas, so it's up to yo u to experiment and create o n yo ur o wn. Yo ur instructo r will help if yo u
go co mpletely o ff the rails.
Acce pt guidance , but do n't de pe nd o n it . Try to so lve pro blems o n yo ur o wn. Go ing fro m
Acce pt guidance , but do n't de pe nd o n it . Try to so lve pro blems o n yo ur o wn. Go ing fro m
misunderstanding to understanding is the best way to acquire a new skill. Part o f what yo u're learning is
pro blem so lving. Of co urse, yo u can always co ntact yo ur instructo r fo r hints when yo u need them.
Use all available re so urce s! In real-life pro blem-so lving, yo u aren't bo und by false limitatio ns; in OST
co urses, yo u are free to use any reso urces at yo ur dispo sal to so lve pro blems yo u enco unter: the Internet,
reference bo o ks, and o nline help are all fair game.
Have f un! Relax, keep practicing, and do n't be afraid to make mistakes! Yo ur instructo r will keep yo u at it
until yo u've mastered the skill. We want yo u to get that satisfied, "I'm so co o l! I did it!" feeling. And yo u'll have
so me pro jects to sho w o ff when yo u're do ne.
Lesson Format
We'll try o ut lo ts o f examples in each lesso n. We'll have yo u write co de, lo o k at co de, and edit existing co de. The co de
will be presented in bo xes that will indicate what needs to be do ne to the co de inside.
Whenever yo u see white bo xes like the o ne belo w, yo u'll type the co ntents into the edito r windo w to try the example
yo urself. The CODE TO TYPE bar o n to p o f the white bo x co ntains directio ns fo r yo u to fo llo w:
CODE TO TYPE:
White boxes like this contain code for you to try out (type into a file to run).
If you have already written some of the code, new code for you to add looks like this.
If we want you to remove existing code, the code to remove will look like this.
We may also include instructive comments that you don't need to type.
We may run pro grams and do so me o ther activities in a terminal sessio n in the o perating system o r o ther co mmand-
line enviro nment. These will be sho wn like this:
INTERACTIVE SESSION:
Co de and info rmatio n presented in a gray OBSERVE bo x is fo r yo u to inspect and absorb. This info rmatio n is o ften
co lo r-co ded, and fo llo wed by text explaining the co de in detail:
OBSERVE:
Gray "Observe" boxes like this contain information (usually code specifics) for you to
observe.
The paragraph(s) that fo llo w may pro vide additio n details o n inf o rm at io n that was highlighted in the Observe bo x.
We'll also set especially pertinent info rmatio n apart in "No te" bo xes:
Note No tes pro vide info rmatio n that is useful, but no t abso lutely necessary fo r perfo rming the tasks at hand.
T ip Tips pro vide info rmatio n that might help make the to o ls easier fo r yo u to use, such as sho rtcut keys.
WARNING Warnings pro vide info rmatio n that can help prevent pro gram crashes and data lo ss.
T he CodeRunner Screen
This co urse is presented in Co deRunner, OST's self-co ntained enviro nment. We'll discuss the details later, but here's
a quick o verview o f the vario us areas o f the screen:
Co de Edito r Demo
Co ursewo rk Demo
OBSERVE:
var springColors = [ "AF7575", "EFD8A1", "BCD693", "AFD7DB", "3D9CA8" ];
OBSERVE:
var temperatures = [ 47.5, 63.2, 56.7, 53, 51.2 ];
Yo u also learned abo ut o bjects. Yo u learned that an o bject usually describes a thing, fo r instance a pet cat, and
co nsists o f pro perties o f that thing, each o f which has a name and a value:
OBSERVE:
var pickles = {
type: "cat",
name: "Pickles",
weight: 7
};
Let's make a simple HTML page with a JavaScript pro gram to create an array and an o bject, and display their values in
the co nso le. Create a new file in the Co de Edito r as sho wn:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Introduction to JSON</title>
<meta charset="utf-8">
<script src="intro.js"></script>
</head>
<body>
</body>
</html>
Save this ( ) as int ro .ht m l in yo ur javascript 2/ fo lder (to create the fo lder, right-click the Ho m e fo lder in the File
Bro wser, select Ne w f o lde r... o r press Ct rl+n, type the new fo lder name javascript 2, and press Ent e r). As yo u can
see, this web page has no co ntent, but it do es have a link to a JavaScript file, int ro .js. Let's make that next. Click the
Ne w File ( ) ico n in the Co de Edito r to o lbar and type the co de sho wn:
CODE TO TYPE:
var springColors = [ "AF7575", "EFD8A1", "BCD693", "AFD7DB", "3D9CA8" ];
var temperatures = [ 47.5, 63.2, 56.7, 53, 51.2 ];
var pickles = {
type: "cat",
name: "Pickles",
weight: 7
};
window.onload = init;
function init() {
console.log(springColors);
console.log(temperatures);
console.log(pickles);
}
Save this ( ) as int ro .js in yo ur javascript 2/ fo lder. Make sure yo u have int ro .ht m l o pen, and click Preview (
). A new empty bro wser windo w (o r tab) appears.
To see the results o f the JavaScript co de, yo u need to o pen a co nso le windo w. If yo u need to , yo u can review the
video and the guide o n ho w to access the co nso le in yo ur bro wser:
The develo per to o ls are a little different in every bro wser, and they changing frequently as new versio ns o f bro wsers
are released, so yo u may have to be a little industrio us and do so me experimenting o n yo ur o wn. We've created a
basic guide to get yo u started, and a sho rt video that explains ho w to access the develo per to o ls, fo r Safari, Chro me,
and Firefo x, and a video fo r Micro so ft Internet Explo rer 9 .
When yo u o pen the co nso le, if yo u do n't see any results, just relo ad the page (intro .html). Yo u'll see the values o f the
two arrays and the o bject.
Take a careful lo o k at ho w these values are displayed; we'll co me back to co mpare these values with o ther values
sho rtly.
When yo u get data fro m a web service—even if it's just a lo cal file o n yo ur o wn co mputer—yo u need to kno w what
fo rmat the data is in. It co uld be just a plain text file co ntaining wo rds, o r a co mma-separated values (CSV) file, o r even
an XML file (eXtensible Markup Language, a co mmo n data interchange fo rmat).
The fo rmat we'll use in this co urse is called JavaScript Object Notation o r JSON. Why are we using this fo rmat?
Because the data fo rmat is almo st identical to the way yo u write arrays and o bjects in JavaScript, so it's easy fo r yo u (a
human) to read, and easy fo r JavaScript to understand! It's also a co mmo n fo rmat used to represent data in web
applicatio ns.
Let's start by lo o king at an example o f an array and an o bject written in JSON fo rmat. First, here's the springCo lo rs
array written in JSON, and also as we wro te it earlier in o ur Javascript pro gram:
OBSERVE:
JSON: ["AF7575","EFD8A1","BCD693","AFD7DB","3D9CA8"]
JavaScript: var springColors = [ "AF7575", "EFD8A1", "BCD693", "AFD7DB", "3D9CA8" ];
Co mpare the two fo rmats, here and where it printed to the co nso le. Pretty similar, right?
What abo ut the t e m pe rat ure s array?
OBSERVE:
JSON: [47.5,63.2,56.7,53,51.2]
JavaScript: var temperatures = [ 47.5, 63.2, 56.7, 53, 51.2 ];
Again, co mpare these to ho w the array lo o ks when it's printed in the co nso le. It lo o ks the same, right?
OBSERVE:
JSON: {"type":"cat","name":"Pickles","weight":7}
JavaScript: var pickles = {
type: "cat",
name: "Pickles",
weight: 7
};
This time, there's a slight difference: each o f the pro perty names ("type," "name," and "weight") is enclo sed in do uble
quo tatio n marks. Otherwise, the JSON o bject is written the same way as the o bject in JavaScript. No te that the JSON
o bject is different fro m o bjects displayed in the co nso le, but that do esn't affect ho w yo u write the co de to create the
o bjects.
JSON can represent all o f the co re values yo u typically write in JavaScript: strings, numbers, and Bo o lean values, as
well as arrays and o bjects that use these kinds o f values in them. JSON can even represent o bjects that co ntain o ther
o bjects and arrays. Fo r instance, yo u can add the "likes" pro perty to the pickle s o bject, like this:
CODE TO TYPE:
var springColors = [ "AF7575", "EFD8A1", "BCD693", "AFD7DB", "3D9CA8" ];
var temperatures = [ 47.5, 63.2, 56.7, 53, 51.2 ];
var pickles = {
type: "cat",
name: "Pickles",
weight: 7,
likes: ["sleeping", "purring", "eating butter"]
};
window.onload = init;
function init() {
console.log(springColors);
console.log(temperatures);
console.log(pickles);
}
and , and the pickle s o bject in the co nso le no w displays the like s pro perty. If we represent the pickle s
o bject in JSON, it lo o ks like this:
OBSERVE:
{"type":"cat","name":"Pickles","weight":7,"likes":["sleeping", "purring", "eating butte
r"]}
The o nly difference in the JSON representatio n o f the like s pro perty o f the o bject and the Javascript is that in JSON,
the pro perty name is enclo sed in do uble quo tatio n marks ("likes").
So , why wo uld yo u want to represent an o bject o r array in JSON fo rmat, and how do yo u do it? I'm glad yo u asked...
In additio n, yo u have to represent the data abo ut the pets so meho w. Yo u need to sto re the data in a fo rmat that yo ur
web applicatio n can understand. Yo u co uld invent a fo rmat, o r yo u co uld use an existing fo rmat like CSV, XML,
o r...JSON! Using JSON makes getting the data into yo ur applicatio n really co nvenient. Let's lo o k at ho w yo u might
represent a list o f pets available fo r ado ptio n using JSON:
OBSERVE:
["Pickles", "Tilla"]
This is an array o f all the pets available fo r ado ptio n. It includes o nly their names; yo u co uld have much mo re
info rmatio n abo ut each pet if yo u use o bjects. Create a new file that lo o ks like this:
CODE TO TYPE:
[ { "type":"cat",
"name":"Pickles",
"weight":7,
"likes":["sleeping","purring","eating butter"]
},
{ "type":"dog",
"name":"Tilla",
"weight":25,
"likes":["sleeping","eating","walking"]
}
]
Save the file in yo ur javascript 2/ fo lder as pe t s.jso n. Make sure yo u're in HTML mo de, and yo u can click Preview
( ) to see the co ntents o f the file. Of co urse, it's just data, so it wo n't actually do anything.
Make sure to type the co de abo ve just as you see it! Yo u can co py and paste the JSON into yo ur file if yo u
Note want, just to be certain.
We're no t go ing to do anything with this file just yet; we'll get to that in a later lesso n. Fo r no w, we'll learn ho w to turn an
array o f o bjects, just like the o ne abo ve, into a JSON string in yo ur JavaScript.
Let's say yo u have a JavaScript pro gram with two pet o bjects. Update int ro .js as sho wn:
CODE TO TYPE:
var springColors = [ "AF7575", "EFD8A1", "BCD693", "AFD7DB", "3D9CA8" ];
var temperatures = [ 47.5, 63.2, 56.7, 53, 51.2 ];
var pickles = {
type: "cat",
name: "Pickles",
weight: 7,
likes: ["sleeping", "purring", "eating butter"]
};
window.onload = init;
function init() {
console.log(springColors);
console.log(temperatures);
console.log(pickles);
Save it, o pen int ro .ht m l, and . In the co nso le, yo u see the two Pet o bjects yo u created:
No w, let's turn these o bjects into JSON. Update int ro .js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var pickles = new Pet("cat", "Pickles", 7, ["sleeping", "purring", "eating butter"]
);
console.log(pickles);
var picklesJSON = JSON.stringify(pickles);
console.log(picklesJSON);
Save it, o pen int ro .ht m l, and . In the co nso le, yo u see the two Pet o bjects yo u created as befo re, and
under each o ne, their JSON representatio ns:
Co mpare the JavaScript o bjects with the JSON versio ns. They are very similar.
JSON.stringify()
To co nvert a JavaScript o bject to JSON, use the built-in J SON o bject and its metho d, st ringif y():
OBSERVE:
var pickles = new Pet("cat", "Pickles", 7, ["sleeping", "purring", "eating butte
r"]);
var picklesJSON = JSON.stringify(pickles);
First, we create a Pet o bject, pickle s. Yo u've seen this kind o f JavaScript befo re. Then, we use the
st ringif y() metho d o f the J SON o bject, and pass in the pickle s o bject as the argument. We get back a
JSON versio n o f the o bject, which we sto re in the variable pickle sJ SON. We did the same thing with t illa to
turn the t illa o bject into JSON.
The J SON o bject is built-in to JavaScript in all modern bro wsers, just like the do cum e nt o bject,
but yo u need to make sure yo u're using a fairly recent versio n o f yo ur favo rite bro wser. That
Note means IE9 +, Safari 5+, Chro me 18 +, Firefo x 11+, o r Opera 11+. So me o lder versio ns o f so me o f
these bro wsers have the JSON o bject, but fo r this co urse, use o ne o f these versio ns (o r later).
So no w let's create an array o f the two o bjects, and co nvert the array to JSON. Update int ro .js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var pickles = new Pet("cat", "Pickles", 7, ["sleeping", "purring", "eating b
utter"]);
console.log(pickles);
var picklesJSON = JSON.stringify(pickles)
console.log(picklesJSON);
We are passing an array to JSON.stringify(). This is no t a pro blem. As with o bjects, yo u can turn an array into
JSON.
Save it, o pen int ro .ht m l, and . In the co nso le, yo u see the two Pet o bjects as befo re.
Underneath the two o bjects, yo u see the JSON string representatio n o f the array co ntaining the two o bjects:
Co mpare the JavaScript o bjects' appearance with the appearance o f the JSON-fo rmatted array o f o bjects.
JSON is called "JavaScript Object No tatio n" because it lo o ks just like JavaScript o bjects. The advantage is
that a JSON-fo rmatted o bject is a string. That means yo u can sto re it in a file, just like yo u did befo re when
yo u created the pe t s.jso n file.
If yo u co mpare the o utput in the co nso le that sho ws the JSON fo rmatted array to what yo u typed into
pe t s.jso n, yo u'll see they are exactly the same (igno re the extra white space yo u added in the file).
Turning an o bject into a string like this is kno wn as serializing an object. It is incredibly useful because it
allo ws yo u to sto re o bjects in plain text files, o r transfer them between web applicatio ns, even if tho se
applicatio ns are written in different languages. Let's say yo u have a Pet o bject in JavaScript, ho w wo uld yo u
give that o bject to , say, a PHP pro gram? The o nly way yo u can do that is to serialize the o bject. Once the
o bject is serialized, yo u can give it to the PHP pro gram, and if the PHP pro gram kno ws that the fo rmat is
JSON, the pro gram can deserialize the o bject (turn the string back into an o bject again), and vo ila! Yo u've just
transferred an o bject between two pro grams written in entirely different languages. That's co o l.
Deserializing an Object
What if yo u've go t an o bject represented in JSON and yo u want to get the o bject back? Update int ro .js as
sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var pickles = new Pet("cat", "Pickles", 7, ["sleeping", "purring", "eating b
utter"]);
console.log(pickles);
var picklesJSON = JSON.stringify(pickles);
console.log(picklesJSON);
Save it, o pen int ro .ht m l, and . In the co nso le, yo u see the JSON representatio n o f the
pickle s o bject, and belo w that yo u see a new Pet o bject that was co nverted back into an o bject fro m JSON:
OBSERVE:
var pickles = new Pet("cat", "Pickles", 7, ["sleeping", "purring", "eating butte
r"]);
var picklesJSON = JSON.stringify(pickles);
console.log(picklesJSON);
First, we created a new pickle s o bject. Then we co nverted that o bject to JSON using J SON.st ringif y(), and
sto red the result in the variable pickle sJ SON. Next, we printed pickle sJ SON to the co nso le.
OBSERVE:
var anotherPickles = JSON.parse(picklesJSON);
console.log(anotherPickles);
We to o k pickle sJ SON and we passed it to the J SON.parse () metho d. J SON.parse () do es the o ppo site o f
what J SON.st ringif y() do es: it takes a piece o f data fo rmatted as JSON, and co nverts it back into a
JavaScript o bject (o r array). This returns an o bject, ano t he rPickle s, which we print to the co nso le.
It's impo rtant to reco gnize that yo u get back ano ther o bject, not the same o bject as o ur o riginal pickle s
o bject. It's like pickle s, in the sense that it has the same pro perties and pro perty values, but no w we have two
different o bjects, pickle s and ano t he rPickle s.
So , J SON.st ringif y() takes an o bject and se rialize s it into JSON fo rmat; and J SON.parse () takes a piece
o f data in JSON fo rmat and de se rialize s it into a JavaScript o bject.
This allo ws yo u to transfer o bjects (o r arrays) between JavaScript pro grams, between web services and web
applicatio ns, and beyo nd. In the next co uple o f lesso ns, yo u'll see ho w yo u can use JSON with Ajax (a way
o f co mmunicating with external reso urces fro m yo ur JavaScript pro gram) to lo ad and save data fro m yo ur
web applicatio n.
JSON or XML?
Yo u may have heard o f XML o r even used it in web applicatio ns befo re, as a data exchange fo rmat. Yo u might be
wo ndering whether we co uld also use XML to represent o bjects and arrays, and exchange data with web services.
Yes, we co uld. In general, yo u may cho o se to use XML o r JSON to represent yo ur data; it depends o n the co ntext, the
type o f data, the web applicatio n, o r the o ther pro grams o r web services with which yo u might be exchanging data.
The go o d news is that anything yo u can represent in XML, yo u can also represent in JSON. While XML has additio nal
features that execute tasks like validating yo ur data to make sure it's co rrect, the kinds o f data that can be represented
are the same. So why cho o se JSON?
JSON is simpler to parse (that is, it's simpler to co nvert data in JSON fo rmat to a JavaScript o bject o r array). A piece o f
data in JSON fo rmat is typically smaller in size than the same data in XML, and it's so mewhat easier fo r us humans to
read. JSON is well suppo rted in JavaScript and web applicatio n to o ls, but it is no t so well suppo rted in o ther areas (fo r
example, library systems, co ntent management systems, and so o n), where XML has bro ad suppo rt. Fo r instance, yo u
can get text edito rs that are specifically designed to help yo u write XML.
If yo u want to use yo ur data in a variety o f different co ntexts, yo u might cho o se XML. Ho wever, fo r web applicatio ns
specifically, JSON is easier to wo rk with; that's what we'll be using in this co urse.
Take so me time to practice JSON and do the pro ject befo re mo ving o n to the next lesso n. We'll use JSON to sto re and retrieve
values thro ugho ut the rest o f the co urse.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Introduction to Ajax
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
What Is Ajax?
We've mentio ned Ajax a co uple o f times no w. So , what is Ajax and ho w are we go ing to use it?
A few years ago , befo re any websites used Ajax, if yo u wanted to get info rmatio n o n the web, yo u clicked a link, waited
fo r the current page to go away, and then waited fo r a new page to lo ad with the info rmatio n yo u wanted. Fo r instance, if
yo u were using a website like Amazo n and searching a database o f bo o ks, yo u entered a bo o k title in the search bar,
and waited fo r a new page to lo ad with the results. Or if yo u were bro wsing Go o gle fo r search results, each time yo u
clicked "next," yo u waited fo r a new page to lo ad with the results. This is the way we all expect the web to behave mo st
o f the time.
No w imagine yo u are using a deskto p applicatio n like an edito r. When yo u select a wo rd and apply, say, "bo ld" to that
wo rd, yo u do n't have to wait fo r the page to lo ad, right? The wo rd just turns bo ld right then and there. This is the way we
expect applicatio ns to behave; we can see the results o f the actio ns we take immediately, witho ut having to wait fo r a
new page to lo ad with the result.
Ajax is a term that is used to describe a co llectio n o f techniques. These techniques allo w yo u to create web pages that
behave mo re like applications than pages. So me o f these techniques yo u already kno w, like ho w to add and remo ve
elements fro m the DOM so yo u can make yo ur page respo nd to user input, o r ho w to change the style o f elements o n
the fly so yo u can change the way the page lo o ks. Yo u execute all o f that witho ut relo ading the page o r lo ading an
entirely new page.
Ajax is also able to add new data to yo ur web page, again witho ut having to relo ad the page o r lo ad an entirely new
page. That's impo rtant. Let's see what that means exactly; we'll take a lo o k at ho w fo rms wo rk witho ut Ajax first, and
then with Ajax, so yo u can see the difference.
Without Ajax
Imagine o nce again that yo u have a pet ado ptio n center. The main page fo r yo ur ado ptio n center website includes a
fo rm at the to p fo r submitting new pets available fo r ado ptio n, and an area belo w where yo u display all available pets. If
yo u submit a new pet using the fo rm, it wo uld wo rk this way witho ut Ajax:
No w, if yo u wanted to submit a seco nd pet, the same pro cess wo uld be required:
Each time yo u submit data fo r ano ther pet, the PHP script has to recreate the entire page: the HTML and the data. This
means yo u have to wait fo r the entire page to lo ad each time yo u submit a new pet.
With Ajax
No w let's take a lo o k at what this interactio n lo o ks like when we use Ajax:
We take these steps:
No t waiting until yo u submit the fo rm to send data to the server. Instead, the page sends the info rmatio n yo u
type as yo u type it.
Requesting small amo unts o f additio nal data, no t an entirely new page.
No t interrupting yo ur user interactio n experience with the page while the data yo u've typed is sent to the
server. Yo u just keep typing as yo u no rmally wo uld.
Getting the respo nse fro m the server—the wo rds that begin with the letters yo u've typed so far—and
updating the page dynamically by adding tho se wo rds to a list in the page.
It's no t until yo u finish typing and submit yo ur search request that a who le new page is lo aded. Just imagine what this
"suggest" experience wo uld be like if a new page was lo aded each and every time yo u typed a character into the
search bo x—so slo w and anno ying!
The ability to send and receive data while yo u type is called Asynchro no us co m m unicat io n. It means that yo ur
page can request data and wait fo r data witho ut ho lding up the user interface o f yo ur web page. Yo u can keep do ing
what yo u're do ing o n the web page while yo ur page sends and waits fo r and receives data behind the scenes. This
Asynchro no us co mmunicatio n is the "A" in Ajax.
So , yo u've seen the two key features and benefits o f using Ajax in yo ur page:
The ability to send data to and receive data fro m the server witho ut causing the page yo u're o n to hang o r
lo ad a who le new page
The ability to update a page dynamically with new info rmatio n, o ften with the data received fro m a server
A We b Bro wse r Web applicatio ns that use Ajax are built using web techno lo gies, and used within a web
bro wser.
HT ML & CSS Yo u'll need HTML and CSS to create a web page to display the co ntent o f yo ur web
applicatio n in the bro wser.
J SON Yo u'll use the JSON fo rmat fo r the data that yo u'll send, sto re, and retrieve using yo ur web
applicatio n. JSON isn't required fo r Ajax applicatio ns; we co uld use ano ther fo rmat instead, but we'll use
JSON in this co urse.
J avaScript JavaScript is where yo u'll do mo st o f the wo rk when yo u're building a web applicatio n. Yo u'll
use a built-in JavaScript o bject, the XMLHttpRequest o bject, to request and receive data fro m a web server.
Yo u'll see ho w this wo rks in the next lesso n. Yo u'll use JavaScript to get data fro m a fo rm, update a page by
adding and remo ving elements fro m the DOM, as well as updating the style o f yo ur page.
A We b Se rve r The web server receives requests fo r data fro m yo ur web applicatio n, and respo nds with that
data. So metimes this is as simple as ho sting a file co ntaining data o n the web server, and requesting and
receiving all o f the co ntents o f that file. Other times, yo u'll make requests to mo re co mplicated server
applicatio ns that update and get results fro m a database o r retrieve data fro m a web service like Twitter o r
Facebo o k. Fo r this co urse, we'll keep it relatively simple because we're fo cusing o n JavaScript, no t server
script develo pment, but yo u'll get the idea (and yo u can wo rk o n mo re co mplicated server scripts if yo u
decide to take the PHP o r Ruby o n Rails co urses).
So get ready! In the next lesso n, yo u'll be building yo ur first Ajax web applicatio n!
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Your First Ajax Application
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
It's time to build yo ur first Ajax applicatio n! As yo u learned in the previo us lesso n, yo u need these pieces:
T he HT ML & CSS
Let's create a simple HTML file with a little CSS fo r o ur Ajax applicatio n. This applicatio n will lo ad the pets data yo u
entered earlier and then update the web page with that data:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Your First Ajax Application: Pets</title>
<meta charset="utf-8">
<script src="pets.js"></script>
<style>
body {
font-family: Helvetica, Arial, sans-serif;
}
legend {
font-weight: bold;
}
</style>
</head>
<body>
<div>
<fieldset>
<legend>Pets available for adoption</legend>
<div id="pets">
</div>
</fieldset>
</div>
</body>
</html>
Save this file in yo ur /javascript 2 fo lder as pe t s.ht m l and click . Yo u see the web page, it lo o ks like
like this:
We link to a JavaScript file named pe t s.js in the head o f the HTML; this is where yo u'll write JavaScript that uses Ajax
to get the pets data and update the page. We'll update the page by adding the JSON we get fro m the pe t s.jso n file to
the " pe t s" <div> in the bo dy o f the HTML. We'll get to that in a bit.
In the HTML, we use a <fieldset> element. In case yo u haven't seen it befo re, yo u can use it to create a gro up o f items.
It's o ften used with fo rms to gro up fo rm elements to gether. The text in the <legend> element is displayed at the to p o f
the fieldset. Our list o f pets will appear inside the fieldset o nce we start adding pets to it. We added a bit o f CSS to
change the fo nt to a sans-serif fo nt (which lo o ks better o n co mputer screens) and to make the legend text bo ld.
T he JSON data
Next, o pen the /javascript 2/pe t s.jso n file yo u created in the first lesso n in HTML mo de, and click . We'll
use this JSON file as input data fo r yo ur first Ajax applicatio n. Yo u see a web page with the JSON data, it lo o ks like
this:
T he JavaScript
No w co mes the really fun part—the JavaScript! Create a new file and type in this co de:
CODE TO TYPE:
window.onload = init;
function init() {
getPetData();
}
function getPetData() {
var request = new XMLHttpRequest();
request.open("GET", "pets.json");
request.onreadystatechange = function() {
var div = document.getElementById("pets");
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText != null) {
div.innerHTML = this.responseText;
}
else {
div.innerHTML = "Error: no data";
}
}
};
request.send();
}
Save this file in yo ur /javascript 2 fo lder as pe t s.js. Open pe t s.ht m l, and click Preview ( ). Or, if yo ur
pe t s.ht m l web page is already o pen, click Re lo ad (to lo ad the new JavaScript). Yo u see the pets fro m the pe t s.jso n
file in yo ur bro wser; it lo o ks like this:
We're displaying o nly the JSON text, so it do esn't lo o k very pretty, but yo u've just dynamically updated yo ur page with
co ntent lo aded fro m a file using Ajax. Co ngratulatio ns!
Let's take a lo o k at what's go ing o n in this co de. Yo u pro bably reco gnize so me o f it, like where we set up the o nlo ad
handler:
OBSERVE:
window.onload = init;
function init() {
getPetData();
}
function getPetData() {
...
}
First we set up an o nlo ad handle r f unct io n by setting the o nlo ad pro pe rt y o f t he windo w o bje ct to the init
functio n. This tells JavaScript that after the bro wser has co mpleted lo ading the page, it sho uld call the init () functio n.
init () calls ano ther functio n, ge t Pe t Dat a(). ge t Pe t Dat a() is respo nsible fo r getting the data in the pe t s.jso n file
and lo ading it into the page.
No w we'll take a clo ser lo o k at what's go ing o n the ge t Pe t Dat a() functio n.
T he XMLHttpRequest Object
The XMLHt t pRe que st o bject is the heart o f mo st Ajax pro grams. It lets yo u talk to the wo rld o utside the bro wser. Yo u
use it to send requests to a web server to retrieve data. As is the case with so me o ther built-in JavaScript o bjects like
do cum e nt , windo w and J SON, XMLHt t pRe que st is a built-in o bject that is available in all mo dern bro wsers.
XMLHttpRequest is no t suppo rted by IE6 . Still, even tho ugh IE6 isn't used much anymo re, it's wo rth
Note paying so me attentio n to , just in case yo u need to suppo rt it with an Ajax applicatio n. In o ur example,
yo u'll need to test to see whther the bro wser is IE6 and if it is, use the ActiveXObject instead.
OBSERVE:
function getPetData() {
var request = new XMLHttpRequest();
request.open("GET", "pets.json");
request.onreadystatechange = function() {
...
};
request.send();
}
Let's break it do wn. First, we create a new XMLHt t pRe que st o bject and save it in the re que st variable. The
XMLHt t pRe que st o bject co ntains pro perties and metho ds that yo u can use to make requests fo r data and receive
respo nses to tho se requests.
We use XMLHt t pRe que st here to request the data in the file pe t s.jso n. To make the request fo r this data, we have
to create a request; we do that with the re que st .o pe n() metho d. We pass two arguments to this metho d: the m e t ho d
we're using to make the request, in this case GET , and the reso urce we're requesting, in this case the file pe t s.jso n.
This is the same kind o f GET request yo u make with an HTML fo rm: it sends an HTTP request to a web server to "get"
a reso urce. It is also co mmo n to make a request using POST ; the differences between GET and POST are beyo nd the
sco pe o f this co urse.
The re que st .o pe n() metho d do esn't actually send the request; to do that we need to use the metho d
re que st .se nd(). But befo re we actually send the request, we want to indicate what we're go ing to do when we get a
respo nse!
The web server—in this case, the O'Reilly Scho o l o f Techno lo gy web server— will get yo ur request fo r the file
pe t s.jso n. It will respo nd by sending back the data that yo u requested. What do we do when we get a respo nse? Well,
we'll call a functio n that we've assigned to the re que st .o nre adyst at e change pro perty. This is similar to the way
windo w.o nlo ad wo rks; with windo w.o nlo ad yo u assign a functio n to the pro perty that the bro wser will call when the
page has finished lo ading. All yo u do is create the functio n; the bro wser calls it fo r yo u.
re que st .o nre adyst at e change wo rks the same way. We assign a functio n to the o nre adyst at e change pro perty o f
the re que st o bject. We're in essence saying, "When yo ur re ady st at e change s, call this functio n."
So , what's a ready state? Remember, the request object is the XMLHt t pRe que st o bject that yo u created to make all
this Ajax co mmunicatio n happen; when it sends o ut a request, it go es thro ugh a series o f ready states that indicate
whether the respo nse is ready fo r yo u to use. The ready state is o ne o f several po ssible integer values that indicate the
status o f the XMLHttpRequest o bject. Fo r instance, it co uld be sending the request, awaiting a respo nse, o r it co uld be
do ne and ready fo r yo u to pro cess the results. Here are the po ssible values fo r the ready state:
The callback f unct io n is an anonymous function. Yo u've seen ano nymo us functio ns befo re, fo r instance,
when we set the value o f an o bject pro perty to a metho d. The end result is that using an ano nymo us functio n
is exactly the same as using a name, but it bypasses the step where the functio n is defined with a name. Fo r
instance, instead o f:
OBSERVE:
request.onreadystatechange = function() {
...
};
OBSERVE:
request.onreadystatechange = handleRequest;
function handleRequest() {
...
}
In bo th cases, yo u're setting the value o f the o nre adyst at e change pro pe rt y to a functio n; in the first
example, that f unct io n has no nam e , while in the seco nd, it has the name handle Re que st . Using an
ano nymo us functio n just skips the step where yo u name the functio n.
Ano nymo us functio ns are used frequently in JavaScript, so it's a go o d idea to get used to seeing them
written.
function getPetData() {
var request = new XMLHttpRequest();
request.open("GET", "pets.json");
request.onreadystatechange = function() {
var div = document.getElementById("pets");
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText != null) {
div.innerHTML = this.responseText;
}
else {
div.innerHTML = "Error: no data";
}
}
};
request.send();
}
We test both the re adySt at e and the st at us in the callback f unct io n; we need to make sure bo th have a
specific value befo re co ntinuing. The && makes sure that bo th expressio ns are true befo re the bo dy o f the if
statement is run. Let's lo o k at what the re adySt at e and st at us tell us.
The ready state that we're mo st interested in is the DONE state; this indicates that the respo nse is co mplete.
So , in the f unct io n, we're testing to see whether the re adyst at e is equal to the DONE value. But what is
t his?
Yo u've actually enco untered t his befo re; in o bject metho ds, we can refer to the o bject who se metho d we're
using as t his. It refers to this object. It's wo rks the same way here. By setting a f unct io n as the value o f the
o nre adyst at e change pro perty o f the re que st o bject, yo u've created an o bject metho d. So here, t his is the
re que st o bject itself—the o bject that co ntains the metho d.
No w that yo u kno w what t his is, yo u can see that t his.re adySt at e is the ready state o f this object—the
request o bject. We're testing to see if it's DONE, where DONE is a pro perty also defined in the request o bject,
so we can co mpare the value o f the re adySt at e to the value o f the DONE to see if they are equal. If they are,
we kno w we go t a respo nse and that it's co mplete.
We're sending an HTTP request here to o ; the server will respo nd and let us kno w if everything went o kay o r if
so mething went wro ng. This part o f o f the respo nse is put into the request o bject's st at us pro perty. If the
st at us is 20 0 , then we kno w everything went o kay, and we can pro ceed.
When the respo nse is co mplete, and if everything went as planned, the data is put into the request o bject's
re spo nse T e xt pro perty. Again, we refer to the request o bject using t his, so we can access the data using
t his.re spo nse T e xt .
We want to make sure that the re spo nse T e xt actually has so mething in it, so we co mpare re spo nse T e xt
to null. If it's no t null, we kno w there is so me data present, so we update the inne rHT ML pro perty o f the
" pe t s" <div> with the JSON data that we go t fro m the pe t s.jso n file. No tice that we go t the " pe t s" <div> at
the very to p o f the callback f unct io n. When yo u lo ad the page, yo u see this data in the <div> o n the page.
If there was a pro blem and the "pets.jso n" file is empty, o r the re spo nse T e xt didn't get set co rrectly and is
null, then we can put the erro r message into the " pe t s" <div> instead.
Step 1:
Step 2:
Step 3:
Asynchronicity Rocks!
As we learned earlier, o ne o f the benefits o f Ajax is that it is asynchronous. Let's take a clo ser lo o k at exactly what that
means:
OBSERVE:
function getPetData() {
var request = new XMLHttpRequest();
request.open("GET", "pets.json");
request.onreadystatechange = function() {
...
};
request.send();
}
In the functio n ge t Pe t Dat a(), where we create the XMLHt t pRe que st o bject and use it to request data fro m the web
server and get the respo nse, we set up the callback f unct io n to handle the respo nse. But we're no t actually calling
this functio n, in fact, we rely o n the bro wser to call that functio n fo r us when the request o bject has received a respo nse.
We set up the functio n, and then se nd the request. After that, the ge t Pe t Dat a() functio n ends and any o ther co de we
might run after calling ge t Pe t Dat a() co ntinues to run.
Mo re impo rtantly, the bro wser is free to respo nd to user interactio n while we're waiting fo r the respo nse to co me back.
So , let's say yo u have a page that has a lo t o f interactive items o n it; yo u may have menus that expand and co llapse
when yo u mo ve yo ur mo use o n and o ff o f them. If yo ur co de uses XMLHttpRequest to go fetch so me data, while it's
o ff fetching that data, yo u can still interact with the page. Yo u can ho ver yo ur mo use o ver a menu and watch it expand
and then co llapse when yo u mo ve yo ur mo use o ff the menu—meaning that the bro wser is running o ther JavaScript
co de while it's waiting fo r the respo nse. The JavaScript engine in the bro wser isn't hung up waiting fo r the respo nse to
yo ur request fo r data; the bro wser will get the respo nse at so me po int and will call yo ur functio n. In the meantime, yo ur
o ther co de is free to co ntinue to wo rk as no rmal.
This asynchro no us benefit o f Ajax do esn't matter much in o ur little Pets example, but imagine yo u're building an
interactive game, and using XMLHttpRequest to send and receive data. In a highly interactive applicatio n, like a game,
it's especially impo rtant that the user be able to co ntinue to interact with the page while the data is being transferred.
That's where the capacity to send and receive data asynchro no usly really co mes in handy.
If Ajax wasn't asynchro no us, whenever yo u made a request to get so me data, all actio n wo uld be suspended until the
respo nse came back. Fo r small applicatio ns like Pets, and small amo unts o f data like pets.jso n, yo u pro bably
wo uldn't no tice, but if yo u were playing a game, o r sending a request to a web service that has to search a huge
database fo r results, yo u'd really no tice a hangup if the data transfer wasn't asynchro no us.
Yo u can make Ajax synchro no us if yo u really want to —that means as so o n as yo u use the se nd() metho d o f the
request o bject to send the request, all o ther wo rk sto ps. There might be a time when yo u need synchro no us behavio r.
If yo u do , add an extra parameter to the o pe n() metho d, like this:
OBSERVE:
request.open("GET", "pets.json", false);
window.onload = init;
function init() {
getPetData();
}
function getPetData() {
var request = new XMLHttpRequest();
request.open("GET", "pets.json");
request.onreadystatechange = function() {
var div = document.getElementById("pets");
if (this.readyState == this.DONE && this.status == 200) {
var type = request.getResponseHeader("Content-Type");
console.log("Content-type: " + type);
console.log("Status: " + this.statusText);
if (this.responseText != null) {
div.innerHTML = this.responseText;
}
else {
div.innerHTML = "Error: no data";
}
}
};
request.send();
}
Save it, switch o ver to pe t s.ht m l tab, and click ). Yo u see bo th the co ntent-type o f the respo nse and
the status in yo ur co nso le windo w:
Make sure yo u cho o se the Co nso le tab to view the results; if the windo w is narro w, yo u might have to use the dro p-
do wn menu to get to the co nso le. Fo r instance, in Safari 5 and Chro me, it will lo o k like this:
In Safari 6 , yo u access the co nso le by go ing to the Lo g tab in the Web Inspecto r, and clicking o n "Current Lo g":
The results sho w that the co ntent type o f the respo nse data is applicat io n/jso n. The server kno ws it's serving us
JSON data. Typically, yo u'll kno w what co ntent type to expect as a respo nse to a request, but if no t, yo u can use the
co ntent type to determine ho w to handle the respo nse. If yo u're expecting plain text, the co ntent-type will be t e xt /plain;
fo r XML, the co ntent-type will co ntain the letters "xml" (the full co ntent-type can vary). If yo u are using XML, the data fo r
the respo nse will be in the pro perty re spo nse XML, not in respo nseText.
The JavaScript Co nso le and Develo per To o ls in every bro wser tend to change with new bro wser
Note versio ns, so yo ur bro wser to o ls may lo o k different.
Yo u can o nly re que st dat a t hat is se rve d f ro m t he do m ain f ro m which t he re que st is co m ing.
This is a security restrictio n built into bro wsers to keep malicio us data o ut o f yo ur web applicatio n. In the pre-mashup
wo rld, this restrictio n was pretty helpful, but no w that we have so many great web services aro und that serve up all
kinds o f interesting data that yo u co uld use to create the next millio n-do llar mashup, this restrictio n co uld be a
hinderance.
Fo rtunately, there is a wo rkaro und: a se rve r-side script , say, a PHP pro gram, that go es and fetches the data yo u
want fro m a remote web service, and then fo rwards it o n to yo ur web applicatio n. This server-side script, o ften called an
"intermediary" script, can verify the data yo u've requested (if necessary) befo re it ever gets to yo ur web applicatio n.
That way, yo u can make the request to the script (which is running o n the same do main), the script requests the data
fro m the web service, verifies the data, and then hands it back to yo ur web applicatio n. This is transparent to yo ur
JavaScript Ajax co de, but it do es mean yo u'll need to write this intermediary pro gram.
We co vered a lo t o f co mplex Ajax details in this lesso n. Take a break, let it all sink in a bit, and then do the pro ject and the quiz.
Then yo u'll be ready to tackle the To Do List applicatio n co ming up next...
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
The To-Do List Application
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
Yo u've built an Ajax applicatio n that reads JSON data fro m an external file. The ability to retrieve data fro m an external so urce is
o ne key feature o f an Ajax applicatio n; a seco nd feature that's just as impo rtant, is the ability to update yo ur web page (o r web
application) dynamically with that data.
In this lesso n, we'll begin building a To -Do List applicatio n. Eventually this applicatio n will let yo u bo th retrieve and save to -do
list items using a web applicatio n that yo u've built. Fo r this first part o f the To -Do List applicatio n, we'll expand the Pets
applicatio n and actually parse the JSON data, then update yo ur web page using that data.
Yo u'll reco gnize the co de belo w. Feel free to reuse so me o f yo ur pro ject so lutio n, o r, if yo u'd prefer, yo u can start fro m scratch.
We'll assume that yo u're starting o ver, so yo u can create fresh files using the instructio ns belo w.
CODE TO TYPE:
[{"task":"get milk","who":"Scott","dueDate":"today","done":false},
{"task":"get broccoli","who":"Elisabeth","dueDate":"today","done":false}]
Save this file in yo ur /javascript 2 fo lder as t o do .jso n and, in HTML mo de, click . Yo u see
yo ur to -do list JSON data in a web page, it lo o ks like this:
Yo ur web applicatio n will read in these items using Ajax. No tice that there are two to -do items: o ne fo r Sco tt
and o ne fo r Elisabeth. Each item has fo ur pro perties: t ask, who , due Dat e , and do ne . All the pro perty values
are strings, except fo r do ne , which is a Bo o lean.
If yo u had to represent these to -do items as an o bject, what wo uld that o bject lo o k like? Give it so me tho ught
and we'll co me back to that quesio n sho rtly.
fo r no w, all we need is a heading and a <div> element. Create a new file and type in this HTML and CSS:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>To-Do List</title>
<meta charset="utf-8">
<script src="todo.js"></script>
<style>
body {
font-family: Helvetica, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>My To-Do List</h1>
<div id="todoList">
</div>
</body>
</html>
Save this file in yo ur /javascript 2 fo lder as t o do .ht m l and click . Yo u see this web page:
There's no thing in the web page, because we haven't added any co ntent to it yet.
CODE TO TYPE:
window.onload = init;
function init() {
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
var listDiv = document.getElementById("todoList");
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
listDiv.innerHTML = this.responseText;
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
Save it in yo ur /javascript 2 fo lder as t o do .js. Open yo ur t o do .ht m l file (which links to this t o do .js file
with the <script> element at the to p), and click . The JSON data fro m t o do .jso n in yo ur web page,
lo o ks like this:
So far, so go o d. It's the same pro cess we applied to Pets, just revised fo r a To -Do List, and using slightly
different JSON data. Here are the steps we take to get there:
So no w that yo u've go t yo ur hands o n the JSON data, let's do so mething better than just spitting it o ut to the
web page as is. What we'd really like to do is make a nice-lo o king list, right?
Remember, this is a literal object, that is, an o bject yo u typed in literally, rather than created using a co nstructo r. No tice
ho w clo sely this t o do Obje ct resembles the items in the t o do .jso n file yo u created earlier.
When we read in the JSON data and turn the JSON to -do items into JavaScript o bjects, we need so mewhere to put
tho se o bjects; we'll stash them in an array. Fo r this step, we'll take a lo o k at that array in the co nso le. After that, we'll
actually use the array o f o bjects to update the page. Update t o do .js as sho wn:
CODE TO TYPE:
var todos = new Array();
window.onload = init;
function init() {
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
var listDiv = document.getElementById("todoList");
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
listDiv.innerHTML = this.responseText;
parseTodoItems(this.responseText);
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
console.log("To-do array: ");
console.log(todos);
}
Save it, o pen yo ur t o do .ht m l file, and click . Yo u wo n't see anything in the web page no w, but yo u'll
see so me o utput in yo ur develo per co nso le. Remember, if yo u're in Safari o r Chro me, yo u might need to use the
arro w in the Develo per co nso le windo w to access the co nso le:
In the co nso le, yo u see an array o f yo ur to -do o bjects, like this in Safari o r Chro me:
...and like this in Firefo x (using Firebug):
Let's walk thro ugh the new co de. First, we add an empty array, t o do s, to ho ld all the o bjects that we create as we
parse the JSON data fro m the t o do .jso n file:
OBSERVE:
var todos = new Array();
No te that this is a glo bal variable so we can access it fro m within any functio n.
Next, we update the o nre adyst at e change handler in the ge t T o do Dat a() functio n to call the functio n
parse T o do It e m s() and pass in the re spo nse T e xt , which ho lds the JSON data fro m the t o do .jso n file:
OBSERVE:
parseTodoItems(this.responseText);
This functio n is where the magic happens: where we turn JSON fro m a file into real HTML o bjects that dynamically
appear in yo ur web page. Well, it wo n't seem like magic anymo re, because yo u'll kno w ho w to do it.
In parse T o do It e m s, the JSON data is passed in as a variable named t o do J SON. In the functio n we test to make
sure t o do J SON is no t null o r the e m pt y st ring. If the file is empty (that is, there are no to -do s yet), then we have no
JSON to parse and no thing to add to the page. If we try to parse an empty JSON string, we'll get an erro r message. We
can avo id that by testing the string first.
We'll lo o k at strings in mo re detail in a later lesso n, but fo r no w, we're using the t rim () functio n (actually a String
metho d!) o n the string t o do J SON. This gets rid o f extra white space at the beginning o r end o f a string. So if yo u have
a string like this:
OBSERVE:
var myString = " There are a few empty spaces. "
...with empty spaces at the begining and/o r end, after calling m ySt ring.t rim (), yo u'll have a string like this:
OBSERVE:
"There are a few empty spaces."
If the string is empty, then we just return fro m the functio n (that is, the rest o f the functio n isn't executed).
OBSERVE:
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
Once we kno w the string isn't empty, we can parse it using J SON.parse (). This turns the t o do J SON string into an
o bject. In o ur case, we kno w that we actually have an array of objects because that's ho w we designed the data in the
to do .jso n file (take ano ther lo o k at the co ntents o f the file and no tice the [ and ] surro unding the rest o f the data; that
means array). So instead o f putting the result o f the call to J SON.parse () into an o bject variable, we are putting it into
an array variable, the t o do Array.
So , why do n't we just put the result o f the J SON.parse () into the t o do s array? That's a great questio n! And the
answer is: because later o n, we're go ing to add a fo rm to the page to let yo u add new to -do items to yo ur list. We want
to keep the to -do s that are in the file separate fro m the to -do s managed by the page, so we have this tempo rary array,
t o do Array, which we use to get the to -do items fro m the file. Later we'll add them to the t o do s array.
Befo re we try to add items fro m the t o do Array to the t o do s array, we need to make sure there's so mething in it. What
if yo u made an empty array (Like this: [ ]) in yo ur to do .jso n file? That wo uld be a valid JSON string, but it wo uldn't
co ntain any o bjects. We need to check fo r that, and return fro m the functio n if the array is empty.
Okay, at this po int we have an array with at least o ne o bject in it. Here's the next (and final) piece o f co de fro m the
parse T o do It e m s functio n:
OBSERVE:
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
console.log("To-do array: ");
console.log(todos);
No w, we want to put the o bjects in the t o do Array into the t o do s array. We'll iterate (lo o p) thro ugh all the items in the
t o do Array and co py them into the t o do s array. First we sto re the item fro m the t o do Array[i] in the variable
t o do It e m , and then use the Array metho d push() to add the t o do It e m o nto the end o f the t o do s array. (We do n't
actually need the intermediate t o do It e m variable; can yo u see ho w yo u'd do this witho ut it?)
Finally, to make sure the t o do s array has the right stuff in it, we use co nso le .lo g() to display the array in the co nso le.
Here's ho w it lo o ks in Safari and Chro me:
Update the Page with T o-Do Items
The next step is to get the items fro m the t o do s array into yo ur web page. We're creating a to -do list, so let's use an
HTML list to put the items in. First, change t o do .ht m l so that the "to do List" element is a <ul> instead o f a <div>, like
this:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>To-Do List</title>
<meta charset="utf-8">
<script src="todo.js"></script>
<style>
body {
font-family: Helvetica, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>My To-Do List</h1>
<div id="todoList"><ul id="todoList">
</div></ul>
</body>
</html>
Of co urse, the "to do List" list is empty (it has no <li> elements in it) because we're go ing to use JavaScript to add <li>
elements created using the o bjects sto red in the t o do s array. Update t o do .js as sho wn:
CODE TO TYPE:
var todos = new Array();
window.onload = init;
function init() {
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
console.log("To-do array: ");
console.log(todos);
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
}
}
Save it, o pen yo ur t o do .ht m l file, and click . Yo u see the to -do items in yo ur t o do .jso n file displayed
as list items in yo ur web page, like this:
Try adding ano ther to -do item to yo ur t o do .jso n file and relo ad the page. Do yo u see it?
OBSERVE:
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
In the o nre adyst at e change handler in ge t T o do Dat a(), in additio n to calling parse T o do It e m s(), we're calling
addT o do sT o Page (). This gets called o nly after all the JSON has been pro cessed and all the to -do items have been
added to the t o do s array, so we kno w all the data will be in the array at this po int.
This addT o do sT o Page functio n actually adds the items to the page by adding them to the "to do List" <ul>:
OBSERVE:
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
}
}
In addT o do sT o Page (), first we ge t t he <ul> element with the id "to do List" fro m the page, so we can add a new list
item to it. Then we lo o p t hro ugh all t he o bje ct s in t he t o do s array and create a tempo rary variable t o do It e m
fo r each item in the array. Then we cre at e a ne w <li> e le m e nt element fo r each item using the
do cum e nt .cre at e Ele m e nt () metho d, and set the HTML co ntents o f that element to a string with data fro m the
t o do It e m o bject. The t o do It e m is an object that was created when we parsed the JSON in the to do .jso n file using
J SON.parse (), an o bject that lo o ks so mething like this:
OBSERVE:
var todoObject = {
task: "Get Milk",
who: "Scott",
dueDate: "today",
done: false
};
Yo u can access each pro perty in the o bject as yo u no rmally wo uld, using do t no tatio n. The string we create to add to
the list item uses the pro perties o f the t o do It e m , like t o do It e m .who , t o do It e m .t ask, and t o do It e m .due Dat e .
Once we've set the co ntent o f the <li> element, we can add it t o t he " t o do List " <ul> e le m e nt using the
appe ndChild() metho d.
As so o n as yo u add the element to the page with appe ndChild(), the page updates to reflect the new element and
yo u see yo ur to -do list item.
Did yo u no tice that we're no t passing the t o do s array into the addT o do sT o Page functio n? That's because it's a
glo bal variable, and we do n't need to —we can access it fro m any functio n. We made the t o do s array a glo bal variable
so we co uld access it in bo th the parse T o do It e m s() and addT o do sT o Page () functio ns.
In this lesso n, yo u've bro ught to gether several things yo u already knew abo ut— JSON, o bjects, arrays, using XMLHttpRequest
to get data fro m a file, and updating the page with new elements—to create an Ajax web applicatio n that lo ads to -do items fro m
a file, to do .jso n, and updates a page with tho se to -do items. All these parts wo rk to gether in yo ur web applicatio n to do
so mething that's really kinda co o l!
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Saving Data with Ajax
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
add a fo rm to the HTML page that lets yo u enter new To -Do items and save them in the JSON file.
style yo ur fo rms.
validate yo ur fo rms.
use Ajax to write data to a file.
In this lesso n, we co ntinue building the applicatio n. We'll add a fo rm to the HTML page that lets yo u enter new To -Do
items and save them in the JSON file, so that the next time yo u lo ad the page, yo ur new items will be there. We'll do
so me fo rm validatio n to o , which is always go o d practice (and will be go o d review o f JavaScript techniques). Yo u've
used Ajax to read data fro m a file; in this lesso n, yo u'll see ho w to use Ajax to write data to a file (with a little help fro m
a PHP script).
<!doctype html>
<html>
<head>
<title>To-Do List</title>
<meta charset="utf-8">
<script src="todo.js"></script>
<style>
body {
font-family: Helvetica, Arial, sans-serif;
}
</style>
<link rel="stylesheet" href="todo.css">
</head>
<body>
<h1>My To-Do List</h1>
<ul id="todoList">
</ul>
<form>
<fieldset>
<legend>Add a new to-do item</legend>
<div class="tableContainer">
<div class="tableRow">
<label for="task">Task: </label>
<input type="text" id="task" size="35" placeholder="get milk">
</div>
<div class="tableRow">
<label for="who">Who should do it: </label>
<input type="text" id="who" placeholder="Scott">
</div>
<div class="tableRow">
<label for="dueDate">Due Date: </label>
<input type="date" id="dueDate">
</div>
<div class="tableRow">
<label for="submit"></label>
<input type="button" id="submit" value="submit">
</div>
</div>
</fieldset>
</form>
</body>
</html>
Save it and click . Yo u see the web page, which lo o ks like this:
When yo u preview, the applicatio n lo ads the to -do items yo u already have in the t o do .jso n file, using the
co de yo u added in t o do .js in the previo us lesso n (yo ur list may lo o k a little different fro m mine if yo u've
added o r changed items).
In this versio n o f t o do .ht m l, we added a fo rm with three data inputs and a submit butto n. Two o f the data
inputs are fo r text—the task, and who sho uld do it—and the third is fo r a date. In many bro wsers, the date field
will lo o k like a text input, but in so me bro wsers yo u may get a po pup date picker.
We've added so me extra <div>s fo r styling the table; yo u'll see ho w this wo rks in the next step.
body {
font-family: Helvetica, Arial, sans-serif;
}
legend {
font-weight: bold;
}
div.tableContainer {
display: table;
border-spacing: 5px;
}
div.tableRow {
display: table-row;
}
div.tableRow label {
display: table-cell;
text-align: right;
}
div.tableRow input {
display: table-cell;
}
Save this file in yo ur /javascript 2 fo lder as t o do .css. Open t o do .ht m l, which links to this t o do .css file
at the to p (with the <link> element), and click . Yo ur page no w lo o ks like this:
The fields are aligned pro perly in this fo rm which gives it a mo re pro fessio nal appearance than the previo us
o ne.
This CSS uses table display pro perties to lay o ut the <div>s and fo rm co ntro ls, similar to the way an HTML
table is laid o ut. If yo u're no t familiar with table display, do n't wo rry abo ut it; it's just a way o f laying o ut a page.
In fact, CSS table display pro perties are the same pro perties that are used by default in the bro wser to lay o ut
HTML tables. Using table display wo rks really well fo r fo rms, because fo rms o ften have a regular, table-like
structure to them.
No w that yo u've go t the web page ready to go fo r entering new to -do items, it's time to update yo ur JavaScript
so that yo u can do so mething with the items yo u enter.
In this next step, we need to get the data fro m the fo rm and check it. We want to get the fo rm data using o ur
o wn JavaScript co de so we can validate the data and then do so mething with it. So , we'll need a way to
determine when the submit butto n is clicked so we can get the data that was entered into the fo rm. We'll add a
click handle r functio n to the Submit butto n.
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueD
ate;
ul.appendChild(li);
}
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
console.log("New task: " + task + ", for: " + who + ", by: " + date);
}
function checkInputText(value, msg) {
if (value == null || value == "") {
alert(msg);
return true;
}
return false;
}
Save it, o pen t o do .ht m l, and click . Open the develo per co nso le so yo u can see the co nso le
message we're using to display the data. Then, type so me data into the fo rm. After entering the data and
clicking subm it , yo u see the co nso le message sho wing the data yo u entered. It lo o ks like this:
We get the data fro m the fo rm with the functio n ge t Fo rm Dat a(), which we set up as the click handle r fo r the
subm it but t o n:
OBSERVE:
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
In ge t Fo rm Dat a(), we check to make sure yo u enter values fo r the vario us fields:
OBSERVE:
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
console.log("New task: " + task + ", for: " + who + ", by: " + date);
}
We get the value o f each field in the fo rm using do cum e nt .ge t Ele m e nt ById() to first get the fo rm co ntro l
element, and then using the element's value () metho d to get the value o f the co ntro l. In o ur fo rm, each
co ntro l will be a string. If any o f the fields is empty, the functio n just re t urns, witho ut displaying anything in the
co nso le. No tice that we're using a helper functio n, che ckInput T e xt () to make sure that each fo rm co ntro l
has a value:
OBSERVE:
function checkInputText(value, msg) {
if (value == null || value == "") {
alert(msg);
return true;
}
return false;
}
che ckInput T e xt () has two parameters: value and m sg. The value is the string data that yo u enter into the
fo rm co ntro l, and the m sg is the text to display in the alert if yo u didn't enter anything fo r that co ntro l. We check
to make sure value isn't empty; if it is, we display the m sg and return true. If it is no t empty, we return false.
Lo o k back at ge t Fo rm Dat a() and yo u'll see that if we return false fro m che ckInput T e xt () fo r each fo rm
field, then we display t he co nso le m e ssage sho wing the data that yo u entered into the fo rm.
Ho w can we create a to -do o bject fo r the data that yo u've entered in the fo rm? We need a to -do o bject
co nstructo r. Do yo u remember ho w to create a co nstructo r functio n? Mo dify t o do .js as sho wn:
CODE TO TYPE:
function Todo(task, who, dueDate) {
this.task = task;
this.who = who;
this.dueDate = dueDate;
this.done = false;
}
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueD
ate;
ul.appendChild(li);
}
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
console.log("New task: " + task + ", for: " + who + ", by: " + date);
var todoItem = new Todo(task, who, date);
todos.push(todoItem);
}
Here, we use the T o do () co nstructo r functio n to create a new T o do o bject, and push that o bject o nto the end
o f the t o do s array. The T o do () co nstructo r takes three arguments: t ask, who , and due Dat e . The
co nstructo r functio n sets the o bject pro perties to the values that are passed into it. In ge t Fo rm Dat a(), we
pass in the values that we go t fro m the fo rm fo r tho se arguments. In additio n, the T o do () co nstructo r sets the
do ne pro perty to f alse . Can yo u guess why?
We'll go back to the do ne pro perty in a later lesso n, but fo r no w assume that all the to -do items are still to be
do ne!
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueD
ate;
ul.appendChild(li);
}
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
console.log("New task: " + task + ", for: " + who + ", by: " + date);
var todoItem = new Todo(task, who, date);
todos.push(todoItem);
addTodoToPage(todoItem);
}
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
document.forms[0].reset();
}
Save it, o pen t o do .ht m l and click . Enter the info rmatio n fo r a to -do item in the fo rm and click
subm it , and yo ur new item appears in the page:
To add the to -do item to the page, we use a new functio n, addT o do T o Page (), and call it just after we add
the new T o do o bject to the t o do s array in the functio n ge t Fo rm Dat a().
OBSERVE:
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
document.forms[0].reset();
}
The To do o bject that's passed into addT o do T o Page () gets the parameter name t o do It e m . To add it to
the page, first we get the "to do List" <ul>; then we create a new <li> element to add to this list, and set the
innerHTML o f the <li> to the info rmatio n in the t o do It e m o bject. Then we add the new list item to the
"to do List" list. Finally, we reset the fo rm to make it easier to create ano ther to -do item using the fo rm.
Do n't mix up the two functio ns: addT o do T o Page (), which we just created to add a single to -do item entered
using the fo rm to the page, and the functio n we added earlier (in the previo us lesso n), addT o do sT o Page (),
which adds all the to -do items in the JSON file, t o do .jso n, to the page when the page first lo ads. The two
functio ns lo o k similar (and have similar names), but have slightly different purpo ses. Can yo u see why we
need bo th? Can yo u think o f ho w we might make o ur co de mo re efficient by co mbining so me o f the
functio nality o f the two functio ns? We'll co me back to these and o ther pressing questio ns in the next lesso n!
We can use Ajax to send the data in the t o do s array (where all o f o ur to do s are sto red) to a PHP script sto red
o n the server (the same server where yo ur t o do .jso n file is sto red, in o ur case, the O'Reilly Scho o l o f
Techno lo gy server) that will write the to -do items into the t o do .jso n file.
Yo u might be wo ndering, "Why can't we write the to -do items directly to the file using JavaScript?" Currently,
JavaScript do es no t allo w yo u to read fro m o r write to files directly o n a filesystem, fo r security purpo ses.
(Imagine if yo u went to a malicio us website that co uld read o r write fro m yo ur hard drive. Bad news!) That's
why we need the help o f a server script.
Wo rk is underway o n a file system library fo r JavaScript that will allo w yo u to read and write files
Note o n yo ur lo cal file system (yo ur o wn co mputer, no t the file system o n the server!), but this is a
new pro ject still in develo pment as o f this writing.
So yo u may think that we read directly fro m the t o do .jso n file. Actually we do n't. We use an HT T P re que st
to get the data, and the web server reads the data fro m the file and returns it to the bro wser, which places the
data in the XMLHt t pRe que st o bject, and o ur JavaScript co de gets it fro m there. Whew!
We need to do so mething similar, o nly in reverse to write the data. We'll use the XMLHt t pRe que st o bject to
send so me data back to the web server. But we need a pro gram o n the web server that kno ws what to do with
the data. We can't just rando mly send data to the web server; we need to send it to a specific pro gram—the
server script—that kno ws ho w to update yo ur t o do .jso n file.
It's really similar to what happens when yo u submit a fo rm that has a server specified in the act io n attribute,
but instead o f using the submit actio n with the fo rm, we'll send the data to the server using the
XMLHt t pRe que st o bject.
Here's ho w it wo rks:
Creating the PHP Server Script
First, we need to create the script we're using to save the to -do items we send it in the t o do .jso n file.
Yo u can switch into PHP mo de to add the PHP file if yo u like; just use the pull-do wn menu to use PHP instead
o f HTML:
CODE TO TYPE:
<?php
$myData = $_GET["data"];
$myFile = "todo.json";
$fileHandle = fopen($myFile, "w");
fwrite($fileHandle, $myData);
fclose($fileHandle);
?>
Do n't wo rry, fo r this co urse, we do n't expect yo u to kno w PHP (altho ugh, if yo u want to learn, there's a great
co urse o n PHP and ho w to write web pages that use PHP scripts: Intro ductio n to PHP. Fo r info rmatio n abo ut
this and all O'Reilly Scho o l o f Techno lo gy co urses, co ntact info @o reillyscho o l.co m.
Anyway, since yo u kno w JavaScript, yo u can pro bably read thro ugh this PHP script and understand so me o f
it. The first part o f the file is respo nsible fo r stripping tho se extra slashes that get added to yo ur string when
yo u send a string that co ntains quo tatio n marks. The next part is an erro r handling functio n (cust o m Erro r),
and belo w that is where the script actually gets the data fro m the request and writes it to the file. Again, do n't
wo rry abo ut these details, just be aware that this script writes all the to -do items yo u send it to the file
t o do .jso n (o verwriting what was there befo re, which will be impo rtant in just a mo ment when we co nsider
what data to send the script fro m o ur JavaScript).
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueD
ate;
ul.appendChild(li);
}
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
console.log("New task: " + task + ", for: " + who + ", by: " + date);
var todoItem = new Todo(task, who, date);
todos.push(todoItem);
addTodoToPage(todoItem);
saveTodoData();
}
function saveTodoData() {
var todoJSON = JSON.stringify(todos);
var request = new XMLHttpRequest();
var URL = "save.php?data=" + encodeURI(todoJSON);
request.open("GET", URL);
request.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8");
request.send();
}
Save it, o pen t o do .ht m l, and click . Add o ne o r two to -do items. No w, relo ad the page. They
sho uld still be there. Open yo ur t o do .jso n file; yo u sho uld see all yo ur to -do items there. (Make sure yo u
clo se it again befo re yo u add any mo re to -do items).
Let's go o ver ho w this wo rks, step-by-step. We added a new functio n save T o do Dat a() where all the actio n
happens. We call this functio n fro m ge t Fo rm Dat a(), after we add a t o do It e m to the page. Let's see what
save T o do Dat a() do es:
OBSERVE:
function saveTodoData() {
var todoJSON = JSON.stringify(todos);
var request = new XMLHttpRequest();
var URL = "save.php?data=" + encodeURI(todoJSON);
request.open("GET", URL);
request.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8");
request.send();
}
We do n't pass any arguments to save T o do Dat a(). Why? Because save T o do Dat a() uses the t o do s
array, which is a glo bal variable, so we do n't need to pass it. First, save T o do Dat a() co nverts the t o do s
array to a JSON string using J SON.st ringif y(). The J SON.st ringif y() metho d takes an o bject o r an array
and turns it into a string that we can then send as data to a script and save in a file. We save that JSON string
in the variable t o do J SON. We put every to -do item in the t o do s array in the JSON string (because the
save .php script o verwrites the entire t o do .jso n file each time we call it; yo u'll see mo re o n this in a
mo ment).
Next, we create a new XMLHt t pRe que st o bject, and save it in the variable re que st . Just like when we used
XMLHttpRequest to send a request to get data fro m a server, we will use XMLHttpRequest to send a request
to send data to a server. We send that data alo ng with the request. There are two ways to send data to a
server script using Ajax: GET and POST. (These are the same GET and POST yo u can use with fo rms to send
data to a server). Let's take a quick lo o k at each o f these metho ds o f sending data.
GET
When yo u use GET to send a request with data to a server, yo u create a URL that lo o ks very similar to the
URLs yo u typically use, except yo u add so me data o n the end o f the URL. Yo u append the data yo u want to
send o n the end o f the URL, with a "?" between the URL o f the script and the data. This is the metho d we use
in save T o do Dat a(). We append the to -do items to the end o f the URL fo r the request to save .php. But we
can't just send the data as is, because it co ntains all kinds o f characters that wo uld mess up the URL and
co nfuse the bro wser and the server. So , we have to enco de the URL first so all the characters that wo uld
co nfuse the bro wser and the server get replaced with their enco ded versio ns. Fo r instance, the enco ded
versio n o f a space (" ") is %20 , and the enco ded versio n o f a do uble quo te (") is %22.
In save T o do Dat a(), we create the URL fo r the request like this:
OBSERVE:
In the first part o f o ur URL, we specify the name o f the script we want to send the request to : save .php.
This URL is a re lat ive URL, which means we do n't specify the full http://... URL. Rather, we just specify the
name o f the script, save .php, because we kno w it's in the same directo ry fro m which the web applicatio n is
lo aded. This request will be co nverted auto matically to a request fo r the full URL fo r us, which is pretty
co nvenient Yo u co uld use the full URL (kno wn as the abso lut e URL) if yo u wanted, but that's no t required.
After the name o f the script, we add ? to separate the path to the script fro m the data we're sending to the
server.
Next, we type dat a=, which gives a name to the data we're sending. This is just like a variable name, and it
allo ws the PHP script to use that name to GET the data fro m the URL. In the PHP script, we had the line:
OBSERVE:
$myData = $_GET["data"];
The result o f this PHP co de is that it can access the data in the URL and sto re it in the variable $ m yDat a,
which it then uses to write the data to the file, t o do .jso n.
Finally, we append the data in the t o do J SON string to the end o f the URL variable. Remember that
t o do J SON is co mprised o f all the to -do items in o ur t o do s array, co nverted into JSON.
We e nco de the t o do J SON string using the built-in JavaScript functio n, e nco de URI(), which takes a string
and turns it into a fo rm suitable fo r sending as part o f a URL with a GET request. So the actual URL that we
use in the XMLHttpRequest lo o ks so mething like this:
OBSERVE:
save.php?data=%5B%7B%22task%22:%22get%20milk%22,%22who%22:%22Scott%22,%22dueDate
%22:%22today%22,%22done%22:false%7D,%7B%22task%22:%22get%20broccoli%22,%22who%22
:%22Elisabeth%22,%22dueDate%22:%22today%22,%22done%22:false%7D,%7B%22task%22:%22
get%20balsamic%20vinegar%22,%22who%22:%22Georgia%22,%22dueDate%22:%222012-08-04%
22,%22done%22:false%7D%5D
POST
If we want to use POST instead o f GET to send the data to the server we can (altho ugh in this particular
example, we'd have to change the co de in the PHP script to o , so it wo n't wo rk). In general, in o rder to use
POST, instead o f putting the data in the URL itself, we send it using the request o bject. We use a similar fo rmat
fo r the data (an enco ded string), but rather than adding it to the URL string, we send it when we call
re que st .se nd().
OBSERVE:
function saveTodoData() {
var todoJSON = JSON.stringify(todos);
var request = new XMLHttpRequest();
var URL = "save.php?data=" + encodeURI(todoJSON);
request.open("GET", URL);
request.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8");
request.send();
}
To send the request, we use the re que st .o pe n() metho d to tell the XMLHt t pRe que st o bject which URL we
want to send the request to , and the kind o f request it is (in this case, it's a GET request). Unlike the request
we sent to retrieve the data in the t o do .jso n file, we do n't have to set up an o nre adyst at e change handler
no w, because we're no t expecting any data back fro m the server (altho ugh in so me situatio ns, yo u might
expect so me data back; it's po ssible to bo th send and receive data in the same request).
So we're almo st ready to send the request, but first we need to tell the server the kind o f data we're sending.
There are many kinds o f data we co uld send in a request: XML, HTML, plain text, binary data (like if we're
sending an image), and so o n. In o ur case, we're just sending plain text: a JSON string.
Any request sent fro m a bro wser to a server co ntains a lo t o f info rmatio n. A request always has a he ade r that
includes info rmatio n like the kind o f request we're making (called the request "metho d," in o ur case, this is
GET), info rmatio n abo ut the enco ding, info rmatio n abo ut the bro wser sending the request, and the URL to
which we're sending the request.
To tell the server the type o f data we're sending with the request, we se t o ne o f t he f ie lds in t he he ade r
named Co nt e nt -T ype , and pro vide the co rrect value fo r plain text. This value co nsists o f a MIME type,
"text/plain" (a string that represents the type o f the file that co nfo rms to the MIME standard o f file types), and a
"charset" that tells the bro wser the character enco ding o f the data. This enco ding is usually "UTF-8 " which is
the current standard fo r enco ding characters (because, amo ng o ther capabilities, it can enco mpass all the
languages in the wo rld).
So no w we're ready to send! We call re que st .se nd() to send the request to the server, which sends all the
data we want to save in t o do .jso n alo ng with the request.
When we send the request, the save .php script is called o n the server, which saves the to -do data into the file
t o do .jso n. If yo u recall, we mentio ned that the script o verwrites anything that's already in this file. That's the
reaso n we send all o f the to -do items each time we make the request. It's no t the mo st efficient way to do it,
but it's a bit less co mplex and a go o d place fo r us to start. Can yo u think o f a better way to do it?
Yo u've updated the applicatio n so that yo u can add new items to yo ur to -do list, and saved tho se items back to the
t o do .jso n file. Yo u can see ho w Ajax is a po werful way to increase the functio nality and flexibility o f yo ur web
applicatio ns. The To -Do List applicatio n is much mo re useful with the ability to save yo ur to -do items, do n't yo u think?
Ho wever, o ur so lutio n isn't perfect by any means. What happens if mo re than o ne perso n uses this web applicatio n?
Fo r instance, if yo u give the URL o f the web applicatio n to a friend, will they be able to see yo ur to -do items? Will they
be able to add items to your to -do list?
Creating a to -do list applicatio n that can suppo rt mo re than o ne user is beyo nd the sco pe o f this co urse, but yo u no w
have a so lid understanding o f what Ajax is, and ho w Ajax wo rks.
So no w yo u kno w that Ajax is a set o f techniques that allo w yo u to create what we call "single-page web
applicatio ns"—web applicatio ns that yo u can use much like a regular deskto p applicatio n. With Ajax techniques, yo u
can update a web page dynamically with new data, yo u can change the page when the user interacts with it, and yo u
can retrieve and save data the user enters into the page, all witho ut having to "lo ad" ano ther web page.
In the next lesso n we'll lo o k at ho w to make JavaScript applicatio ns better by adding so me efficiency to the To -Do List
applicatio n.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Document Fragments
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
As yo u no w kno w, a big part o f writing Ajax applicatio ns, and in fact, mo st JavaScript web applications, is the ability to add and
remo ve elements fro m the page dynamically, using JavaScript. In the To -Do List applicatio n, we add elements fro m the
t o do .jso n file to the page when we first lo ad the page, and we add a new to -do item each time the user enters data into the
fo rm and clicks the subm it butto n.
Each time yo u add (o r remo ve) an element fro m a web page, yo u're accessing the DOM tree—the Do cument Object Mo del tree
—that is the internal representatio n o f the web page in the bro wser. Fo r fairly small applicatio ns like this o ne, accessing the
DOM do esn't cause any pro blems because it's no t happening to o frequently. Ho wever, fo r large applicatio ns, frequent access
to the DOM can beco me a pro blem; each time yo u access the DOM, the bro wser has to sto p everything and update the entire
page. If yo u have hundreds o r even tho usands o f elements in a page, and yo u're adding and remo ving elements frequently, all
tho se DOM o peratio ns can really slo w things do wn.
One way we can make adding new elements to a page mo re efficient is to use document fragments.
The best way to understand do cument fragments, o f co urse, is to dive right in and start making them. Let's start by
creating a small page with so me simple JavaScript:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Document Fragments</title>
<meta charset="utf-8">
<script src="frag.js"></script>
<style>
div.box {
position: relative;
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div id="container">
</div>
</body>
</html>
Save it in yo ur /javascript 2 fo lder as f rag.ht m l. This page do esn't co ntain much o f anything, so do n't preview until
after yo u've added the JavaScript to create the new elements and add them to the page.
Take no te o f the CSS in the do cument. We'll use this CSS to style the new <div> elements with the class "bo x" that we'll
add to the page using JavaScript. Create a new file and type in this JavaScript:
CODE TO TYPE:
window.onload = init;
function init() {
var colors = [ "red", "blue", "green" ];
var container = document.getElementById("container");
for (var i = 0; i < 3; i++) {
var box = document.createElement("div");
box.setAttribute("class", "box");
box.style.backgroundColor = colors[i];
container.appendChild(box);
}
}
Save it in yo ur /javascript 2 fo lder as f rag.js. Open yo ur f rag.ht m l file, which links to this f rag.js file at the to p
(with the <script> element), and click . Yo u see a page like this:
The JavaScript here is fairly straightfo rward; we use a lo o p to create three new <div> elements, each with the class
"bo x", and add them o ne at a time to the DOM by calling
OBSERVE:
container.appendChild(box);
Because co nt aine r is an element in the DOM, each time yo u call appe ndChild(bo x), yo u add a new <div> element
directly to the DOM:
The first time thro ugh the lo o p, we add the red "bo x" <div>:
Fo r o ur small lo o p o f three <div> elements, this is fine, but imagine yo u're adding 10 0 o r 10 0 0 <div> elements, like
yo u might if yo u were, say, initializing the state o f a game applicatio n that has many elements representing game
co mpo nents. In that case, updating the DOM fo r each individual element like this will slo w do wn the initializatio n
pro cess substantially.
A do cument fragment is an empty no de. It's just like the no des yo u find in a DOM tree—where "no de" just means an
element o bject in the tree—except that it do esn't represent any specific element. It's a co mpletely generic no de that
do esn't mean anything. It's po werful tho ugh, because yo u can add elements to the fragment as children, just like yo u
can with a DOM tree.
Let's update the co de fo r the example to use a do cument fragment. Mo dify f rag.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var colors = [ "red", "blue", "green" ];
var container = document.getElementById("container");
var fragment = document.createDocumentFragment();
for (var i = 0; i < 3; i++) {
var box = document.createElement("div");
box.setAttribute("class", "box");
box.style.backgroundColor = colors[i];
container.appendChild(box);
fragment.appendChild(box);
}
container.appendChild(fragment);
}
Save it, then o pen yo ur f rag.ht m l file and click . Yo ur page will lo o k exactly the same, but it will be just
a tiny bit mo re efficient. We like that.
OBSERVE:
function init() {
var colors = [ "red", "blue", "green" ];
var container = document.getElementById("container");
var fragment = document.createDocumentFragment();
for (var i = 0; i < 3; i++) {
var box = document.createElement("div");
box.setAttribute("class", "box");
box.style.backgroundColor = colors[i];
fragment.appendChild(box);
}
container.appendChild(fragment);
}
We still need to get the "co ntainer" <div> fro m the DOM because we'll use it later. After we get that, we cre at e an
e m pt y do cum e nt f ragm e nt using do cum e nt .cre at e Do cum e nt Fragm e nt (). Think o f it like an empty element
just hanging o ut in yo ur pro gram, separate fro m the DOM, except that it do esn't actually represent any specific element;
it's just a generic no de waiting fo r yo u to add things to it.
Next we lo o p, as befo re, o nly this time, each time thro ugh the lo o p, we add a ne w " bo x" <div> to the f ragm e nt
rather than the DOM. We still use the appe ndChild() metho d as usual, because a fragment o bject is just like an
element o bject and as such, it has all the same metho ds.
Once we've co mpleted the lo o p, here's what we've go t: we have a DOM tree with an empty "co ntainer" <div>, and a
do cument fragment sitting o ff separately fro m the DOM with three "bo x" <div>s, waiting to be added to the DOM:
Finally, we add t he e le m e nt s in t he f ragm e nt t o t he DOM by calling appe ndChild() again. No tice that we're
calling appe ndChild() o n t he " co nt aine r" <div> and passing t he f ragm e nt as t he argum e nt .
No w this is where adding a fragment to the DOM differs a little fro m adding an element to the DOM using
appe ndChild(). Instead o f appending the fragment alo ng with everything co ntained in the fragment to the DOM, o nly
the elements co ntained in the fragment are mo ved to the DOM, like this:
...which is perfect, because no w o ur "co ntainer" <div> co ntains the three "bo x" <div>s just like we want! All the new
elements are added to the DOM in one o peratio n, instead o f the three separate o peratio ns that we needed befo re
when we weren't using the fragment. This is a lo t mo re efficient, and we're left with an empty fragment (which yo u can
even use again if yo u want). Can yo u think o f any reaso n yo u wo uldn't want the actual fragment in the DOM?
Using Document Fragments in the T o-Do List Application
No w that yo u kno w ho w to use a do cument fragment, let's see if we can impro ve the To -Do List applicatio n we've
been building. If yo u remember fro m the previo us lesso ns, the To -Do List applicatio n adds to -do items to the page
when the page is first lo aded by reading them fro m a file using XMLHttpRequest, and then adding each item to the page
in a lo o p fro m an array.
Also , recall that we have two very similar functio ns in o ur JavaScript: addT o do sT o Page (), which is the functio n that's
called when the page is lo aded to add the to -do items fro m t o do .jso n to the page, and addT o do T o Page (), which
is the functio n that's called when yo u add a to -do item using the fo rm.
OBSERVE:
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
}
}
...and:
OBSERVE:
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
document.forms[0].reset();
}
The main difference is that addT o do sT o Page () adds to -do items to the page fro m the t o do s array, while
addT o do T o Page () adds the o ne t o do It e m that's passed into the functio n. But o ther than that, they bo th essentially
do the same thing: they each cre at e a ne w <li> e le m e nt , and add it t o t he DOM. addT o do sT o Page () do es this
each time thro ugh the lo o p, while addT o do T o Page () do es it o nce.
We can impro ve this co de in two ways. First, we can co mbine so me o f the co mmo n co de that is used by bo th
functio ns so we aren't repeating o urselves. Seco nd, we can make addT o do sT o Page () mo re efficient by adding all
the to -do items to a do cument fragment befo re adding them to the DOM.
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = createNewTodo(todoItem);
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueD
ate;
ul.appendChild(li);
}
}
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = createNewTodo(todoItem);
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
document.forms[0].reset();
}
function createNewTodo(todoItem) {
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
return li;
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
function saveTodoData() {
var todoJSON = JSON.stringify(todos);
var request = new XMLHttpRequest();
var URL = "save.php?data=" + encodeURI(todoJSON);
request.open("GET", URL);
request.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8");
request.send();
}
Save it, then o pen yo ur t o do .ht m l file and click . Yo ur To -Do List applicatio n sho uld wo rk
exactly the same way as it did befo re; that is, it sho uld lo ad any to -do items yo u have in yo ur t o do .jso n file
when yo u lo ad the page, and yo u sho uld be able to add items using the fo rm.
We to o k the co de that is co mmo n to the addT o do sT o Page () and addT o do T o Page () functio ns:
OBSERVE:
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
ul.appendChild(li);
OBSERVE:
function createNewTodo(todoItem) {
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
return li;
}
No w that the co mmo n co de is in o ne functio n (cre at e Ne wT o do ()) that returns the new <li> element, we can
call this functio n whenever we need to create a new <li> element, and pass in the t o do It e m o bject to use.
So , that's what we did in the addT o do sT o Page () and addT o do T o Page () functio ns. Instead o f having
bo th these functio ns create the <li> element fo r the to -do item, no w they just call cre at e Ne wT o do () to do it
fo r them.
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
var listFragment = document.createDocumentFragment();
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = createNewTodo(todoItem);
ul.appendChild(li);
listFragment.appendChild(li);
}
ul.appendChild(listFragment);
}
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = createNewTodo(todoItem);
ul.appendChild(li);
document.forms[0].reset();
}
function createNewTodo(todoItem) {
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
return li;
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
function saveTodoData() {
var todoJSON = JSON.stringify(todos);
var request = new XMLHttpRequest();
var URL = "save.php?data=" + encodeURI(todoJSON);
request.open("GET", URL);
request.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8");
request.send();
}
Save it, o pen yo ur t o do .ht m l file, and click . Yo ur To -Do List applicatio n will behave the
same way as it did befo re.
No w, instead o f adding each to -do item fro m the t o do .jso n file to the DOM o ne at a time, we're adding them
all in o ne chunk by using a do cument fragment:
OBSERVE:
function addTodosToPage() {
var ul = document.getElementById("todoList");
var listFragment = document.createDocumentFragment();
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = createNewTodo(todoItem);
listFragment.appendChild(li);
}
ul.appendChild(listFragment);
}
Just like we did befo re in o ur simple "bo x" example, we cre at e a f ragm e nt where we can add all the new
to -do items, and then add e ach t o -do it e m in t he lo o p t o t he f ragm e nt . Once we've added all the to -
do items in the t o do s array to the fragment, we can add t he ne w list it e m e le m e nt s in t he f ragm e nt t o
t he DOM. Remember, the elements in the fragment are mo ved fro m the fragment to the DOM, and then the
fragment is left empty, still separate fro m the DOM.
Enhancing the T o-Do List Application
We've made so me subtle impro vements to the To -Do List applicatio n. Next, let's enhance the applicatio n by adding a
little mo re structure to each to -do item in the to -do list and make use o f the do ne pro perty. We'll even add so me CSS
style to make it lo o k better. Mo dify t o do .js as sho wn:
CODE TO TYPE:
function Todo(task, who, dueDate) {
this.task = task;
this.who = who;
this.dueDate = dueDate;
this.done = false;
}
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
var listFragment = document.createDocumentFragment();
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = createNewTodo(todoItem);
listFragment.appendChild(li);
}
ul.appendChild(listFragment);
}
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = createNewTodo(todoItem);
ul.appendChild(li);
document.forms[0].reset();
}
function createNewTodo(todoItem) {
var li = document.createElement("li");
li.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
var spanTodo = document.createElement("span");
spanTodo.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
li.appendChild(spanDone);
li.appendChild(spanTodo);
return li;
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
function saveTodoData() {
var todoJSON = JSON.stringify(todos);
var request = new XMLHttpRequest();
var URL = "save.php?data=" + encodeURI(todoJSON);
request.open("GET", URL);
request.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8");
request.send();
}
If yo u save and run this co de no w, it will run as expected, but wo n't lo o k much different, so yo u might want to wait until
we add the CSS belo w to run it. Befo re we add the CSS, let's step thro ugh the changes in the JavaScript.
li.appendChild(spanDone);
li.appendChild(spanTodo);
return li;
}
First, no tice that we add two <span> elements to the <li> element ho lding each to -do item. Befo re, we simply added
so me text to the inne rHT ML o f the <li> element directly, no w we add two <span> elements: o ne t o ho ld t he t o -do
inf o rm at io n abo ut who needs to do the task, what the task is, and when it's due (the same info rmatio n we added
directly to the <li> element previo usly); and ano ther <span> t o ho ld t he inf o rm at io n abo ut whe t he r an
e le m e nt is do ne o r no t . We're representing the do ne o r no t do ne info rmatio n with a class so we can style the
<span> appro priately (as yo u'll see after yo u add the CSS belo w). If the to -do item is not do ne, we add the class
"no tDo ne" to the spanDo ne <span> element, and if it is do ne, then we add the class "do ne". Also no tice that we're
setting the inne rHT ML o f the "no tDo ne" <span> to five spaces— is the HTML entity fo r a no n-breaking space—
and setting the inne rHT ML o f the "do ne" <span> to two spaces and a check mark—
 0 0 4; is the HTML entity fo r
the check mark. Yo u'll see what this lo o ks like in a minute, after yo u add the CSS and preview again.
After creating the <li> element and the two <span> elements, we add t he t wo <span> e le m e nt s t o t he <li>: first
the spanDo ne , so we can sho w if an item is do ne o r no t o n the left part o f the to -do item, and then the spanT o do ,
which co ntains the text o f the to -do item (the same text as we were sho wing previo usly). Then we return the <li>
element, just like we did befo re. Remember that bo th the addT o do sT o Page () and addT o do T o Page () functio ns
call the cre at e Ne wT o do () functio n, so they get the <li> element back and add it to the DOM, so we can see o ur to -do
item.
Okay, we're almo st there! Mo dify yo ur t o do .css file as sho wn so yo u can see the result o f adding the two <span>
elements to the HTML:
CODE TO TYPE:
body {
font-family: Helvetica, Ariel, sans-serif;
}
legend {
font-weight: bold;
}
div.tableContainer {
display: table;
border-spacing: 5px;
}
div.tableRow {
display: table-row;
}
div.tableRow label {
display: table-cell;
text-align: right;
}
div.tableRow input {
display: table-cell;
}
ul#todoList {
list-style-type: none;
margin-left: 0px;
padding-left: 0px;
}
ul#todoList li {
padding: 5px;
margin: 5px;
background-color: #ededed;
border: 2px inset #ededed;
}
ul#todoList li span.notDone {
margin-right: 10px;
background-color: #FF9999;
}
ul#todoList li span.done {
margin-right: 10px;
background-color: #80CC80;
}
Save it, o pen yo ur t o do .ht m l file, and click . No w yo u sho uld see yo ur to -do items lo o king a lo t
snazzier:
To test the "do ne" vs. "no tDo ne" co de, make sure yo u have o ne to -do item in yo ur t o do .jso n file that uses "true" fo r
the do ne pro perty. Yo u can do this by editing yo ur t o do .jso n file and updating the co de. Fo r instance, I updated the
first item in my file, so my JSON lo o ks like this:
OBSERVE:
[{"task":"Get milk","who":"Scott","dueDate":"today","done":true},
{"task":"Get broccoli","who":"Elisabeth","dueDate":"today","done":false},
{"task":"get balsamic vinegar","who":"Georgia","dueDate":"2012-08-04","done":false},
{"task":"Walk Trish's dog","who":"Scott","dueDate":"2012-06-06","done":false}]
Once yo u've made this change (o r a similar change in yo ur t o do .jso n, which likely has different items in it at this
po int!), either relo ad yo ur already-o pen bro wser windo w with yo ur to -do list applicatio n, o r click o n yo ur
t o do .ht m l file again. Yo u'll see a nice-lo o king green bo x with a check mark indicating that yo ur to -do item is do ne!
Take a lo o k at the CSS to see ho w we styled the to -do items and no tice the two classes, "do ne" and "no tDo ne". We're
using them in o ur JavaScript co de to get the lo o k o f a "do ne" item (a green bo x with a check mark) and a "no tDo ne"
item (a red bo x with no check mark).
No w, yo u've pro bably no ticed that the o nly way to mark an item as "do ne" is to edit yo ur t o do .jso n file, which do esn't
seem like a very user-friendly pro cess. Do n't wo rry abo ut that fo r no w, we'll co me back to this issue in a later lesso n.
As a pro grammer, yo u'll o ften disco ver ways to make yo ur co de better by reducing redundant co de o r making it mo re efficient,
especially as yo u begin to wo rk o n bigger pro jects. We call this refactoring yo ur co de. The main go als o f refacto ring are to
reduce the co mplexity o f yo ur co de, impro ve its readability, and make it easier to maintain. Yo u'll want to get into the habit o f
assessing yo ur co de frquently as yo u build it, to see if it needs refacto ring.
Enjo y the quiz and pro ject, and stay tuned fo r the next lesso n, where yo u'll learn a who le new technique fo r sto ring yo ur to -do
items!
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Introduction to Local Storage
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
In previo us lesso ns, yo u learned ho w to use XMLHttpRequest in yo ur web applicatio n to co mmunicate with a server, bo th to
retrieve data to update yo ur web page, and to save data so that the next time a web applicatio n is lo aded, the data is still there.
XMLHttpRequest is a great so lutio n fo r saving and retrieving lo ng-term data, o r data that yo u want yo ur web applicatio n to be
available to users o f any bro wsers.
So metimes tho ugh, yo u o nly need to sto re data fo r a sho rt perio d o f time, fo r example, preferences fo r a sho pping sessio n.
Other times yo u might want to save so me data that a user has do wnlo aded so that if they return to yo ur page again, yo ur
applicatio n do esn't have to do wnlo ad it again if it's still saved.
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Local Storage</title>
<meta charset="utf-8">
<script src="prefs.js"></script>
</head>
<body>
<h1>Preferences</h1>
<ul id="items">
</ul>
</body>
</html>
Save it in yo ur /javascript 2 fo lder as pre f s.ht m l. The <script> tag includes a link to pre f s.js; let's create that no w:
CODE TO TYPE:
window.onload = init;
function init() {
localStorage.setItem("favGenre", "fiction");
}
Save it in yo ur /javascript 2 fo lder as pre f s.js. Open yo ur pre f s.ht m l file and click . Yo u wo n't see
anything special in the HTML page, so o pen yo ur develo per co nso le and type lo calSt o rage at the JavaScript
co nso le pro mpt:
We created a little bit o f data, with the name " f avGe nre " and the value " f ict io n" , and sto red this data in the o bject
named lo calSt o rage . Befo re we dig into Lo cal Sto rage, mo dify pre f s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
localStorage.setItem("favGenre", "fiction");
var favoriteGenre = localStorage.getItem("favGenre");
var ul = document.getElementById("items");
var li = document.createElement("li");
li.innerHTML = favoriteGenre;
ul.appendChild(li);
}
Save it. In this versio n o f pre f s.js, we delete the first line which creates the " f avGe nre " bit o f data, and replace that
line with new co de that retrieves that bit o f data, using the same name (" f avGe nre " ) to save it, and then add the data
to the web page by creating a new list item and adding the list item to the "items" list (which already exists in yo ur
HTML).
Befo re yo u preview, make sure yo u close the bro wser windo w yo u had o pen when yo u ran the pro gram befo re. We
want to make sure we're starting fro m scratch. No w, o pen pre f s.ht m l, and click . Do yo u see "fictio n" in
the list in the page? Open the develo per co nso le, and again, type lo calSt o rage at the JavaScript co nso le pro mpt:
So even tho ugh yo u closed the bro wser windo w, when yo u retrieve the data yo u sto red in the lo calSt o rage o bject, it
is still there! So , what's go ing o n? Ho w do es the data in the lo calSt o rage o bject survive, even tho ugh yo u clo sed the
bro wser windo w, and relo aded the pre f s.ht m l page?!
Here's the secret: the lo calSt o rage o bject is a built-in JavaScript o bject that saves data in the browser itself. It wo rks
in all bro wsers. As yo u kno w, the bro wser is a pro gram that yo u use to bro wse the web and run web applicatio ns.
When yo u run yo ur web applicatio n in the bro wser, and use Lo cal St o rage to sto re data, the bro wser reserves so me
space just fo r yo ur web applicatio n's data, and makes it accessible to yo ur web applicatio n thro ugh the lo calSt o rage
o bject.
We use the term "Lo cal Sto rage" to refer to the feature o ffered by bro wsers, and "lo calSto rage" to refer
Note specifically to the lo calSt o rage o bject in JavaScript.
Have yo u ever heard o f browser cookies? Well, Lo cal St o rage is similar to co o kies, except that in many ways, it's
way better. We'll co me back to co o kies later, fo r no w we'll fo cus o n Lo cal Sto rage.)
These next few video s sho w ho w to explo re Lo cal Sto rage using bro wser to o ls:
Safari 5
Safari 6
If yo u've upgraded to Safari 6 (fro m Safari 5, the current versio n at the time this co urse was written), yo u'll need to
watch this video :
Safari 6
Internet Explo rer 9 wo rks much like Firefo x, Chro me, and Opera. Select T o o ls | F12 De ve lo pe r T o o ls (o r press
F12) and select Co nso le . Then, type lo calSt o rage and any o ther co mmands at the >> pro mpt.
After watching the video s, make sure yo u try accessing Lo cal Sto rage fro m at least o ne o r mo re bro wsers. Try adding
new items to Lo cal Sto rage fro m the JavaScript co nso le. Make sure yo u kno w ho w to determine what's in Lo cal
Sto rage; it will co me in handy fo r debugging yo ur pro grams later.
Bro wser Develo per To o ls usually change with each new versio n o f a bro wser. While the functio nality
sho uld be the same o r better with each new versio n, user interfaces so metimes changes dramatically.
Note So if yo ur bro wser Develo per To o ls lo o k different fro m the versio ns sho wn in the video s o r screen
sho ts, do n't wo rry. Yo u'll still be able to do the same things, yo u just might have to wo rk a bit to figure o ut
the differences in the UI.
OBSERVE:
localStorage.setItem("favGenre", "fiction");
The lo calSt o rage o bject is similar to o bjects yo u create in JavaScript, except that it's built in to JavaScript, and has
access to native bro wser co de that can sto re and retrieve data fro m the bro wser lo cal sto rage area. The se t It e m ()
metho d allo ws yo u to sto re data. Each item sto red in Lo cal Sto rage has a ke y and a value . This type o f co mputer
sto rage is called an associative array; instead o f using a numerical index (and having a specific o rder based o n that
index) like in a regular array, an asso ciative array uses keys as the index values (and has no inherent o rdering).
The ke y yo u use to sto re the data must be a string; in this case, we used the string " f avGe nre " . Each key must be
unique—if yo u use the same key to sto re ano ther piece o f data, yo u'll o verwrite the previo us piece o f data yo u sto red in
the bucket fo r that key.
When yo u want to retrieve a value fro m Lo cal Sto rage, yo u need to use the same ke y:
OBSERVE:
var favoriteGenre = localStorage.getItem("favGenre");
The lo calSt o rage has a ge t It e m () metho d that yo u use to retrieve a value by its ke y. Here, we're using the
" f avGe nre " ke y to retrieve the bit o f data we just sto red. The value re t urne d is the value yo u sto red; we're putting
that value in the variable f avo rit e Ge nre , which yo u can then use in yo ur pro gram. In the pre f s example, we added
that value to a list in the web page.
CODE TO TYPE:
window.onload = init;
function init() {
var favoriteGenre = localStorage.getItem("favGenre");
addItem("favGenre", "fiction");
addItem("favFlavor", "Vanilla Chocolate Chip");
addItem("book", "Head First HTML5 Programming");
addItem("browserWidth", 1280);
}
Save it, o pen yo ur pre f s.ht m l file, and click . Yo u see fo ur items in the list, and if yo u check yo ur
bro wser's Lo cal Sto rage, yo u see the same fo ur items there:
Try adding a few mo re items using this pro gram by adding mo re calls, with different values, to the addIt e m () functio n.
Relo ad the page. Do yo u see yo ur new items? Try changing the values. Relo ad. Do yo u no tice anything abo ut the
o rdering o f the items in the list, and the o rdering in Lo cal Sto rage?
In this pro gram, we create an addIt e m () functio n to add a key and a value to Lo cal Sto rage, and then call ano ther
functio n, addT o List (), to add it to the page.
We also create a ge t It e m () functio n to get an item fro m Lo cal Sto rage, but we're no t using it yet. Let's do that next.
No w that yo u have a bunch o f items in Lo cal Sto rage, let's co mment o ut the lines to add the items, and add a call to
ge t It e m () so we can see the values that are in Lo cal Sto rage that way. Mo dify pre f s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
//addItem("favGenre", "fiction");
//addItem("favFlavor", "Vanilla Chocolate Chip");
//addItem("book", "Head First HTML5 Programming");
//addItem("browserWidth", 1280);
getItem("book");
}
Save it, o pen yo ur pre f s.ht m l file, and click . Yo u see an alert sho wing the bo o k item yo u go t fro m
Lo cal Sto rage using ge t It e m (). No tice that yo u will not see the items in the list in the web page. Why? Because yo u're
no lo nger calling addT o List () which is called fro m addIt e m (). Try changing "bo o k" to ano ther key, like
"bro wserWidth". Yo u see an alert sho wing yo u the key and the value fo r each key yo u try. What happens if yo u try a key
that do esn't exist in Lo cal Sto rage?
Currently, bro wser implementatio ns o f Lo cal Sto rage o nly sto re string values. So ho w is o ur "bro wserWidth"
value wo rking? Make this change in pre f s.js and then see if yo u can guess ho w it's wo rking:
CODE TO TYPE:
window.onload = init;
function init() {
//addItem("favGenre", "fiction");
//addItem("favFlavor", "Vanilla Chocolate Chip");
//addItem("book", "Head First HTML5 Programming");
//addItem("browserWidth", 1280);
getItem("book""browserWidth");
}
The lo calSto rage o bject co nverted the number, 1280 , that we used as the value fo r the "bro wserWidth" key, to
a string to sto re it in Lo cal Sto rage. Then, when we get the value fo r "bro wserWidth" using ge t It e m (), the
value returned is a string, no t a number, because that's ho w it was sto red. (No te that we used a built-in
JavaScript o perato r, t ype o f , to get the type o f the primitive value returned fro m the call to ge t It e m ().
t ype o f is a unary o perato r: it takes o ne value, and returns its type. This o perato r wo rks o n all JavaScript
primitive types, including numbers, strings, Bo o leans and undefined).
Try adding an item (using addIt e m ()) that co ntains a Bo o lean value. Fo r instance:
OBSERVE:
addItem("isItCold", false);
and then use ge t It e m () to get the value and alert it. What is the type?
So , Lo cal Sto rage uses strings fo r bo th its keys and its values. That means, if yo u sto re ano ther value, like a
number o r a Bo o lean, yo u need to be prepared to co nvert it back to that type fro m a string when yo u retrieve
the value fro m Lo cal Sto rage later!
Indeed it wo uld, and there is a better way. We can iterate thro ugh all the keys in Lo cal Sto rage, witho ut kno wing in
advance what tho se keys are. Mo dify pre f s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
//addItem("favGenre", "fiction");
//addItem("favFlavor", "Vanilla Chocolate Chip");
//addItem("book", "Head First HTML5 Programming");
//addItem("browserWidth", 1280);
getItem("browserWidth");
Save it, o pen pre f s.ht m l, and click . Yo u see every item yo u've sto red in Lo cal Sto rage in yo ur list.
We added a new functio n, sho wAllPre f s(), to sho w us every item in Lo cal Sto rage. This functio n lo o ks thro ugh
everything in Lo cal Sto rage and adds it to the web page by calling addT o List (). We call sho wAllPre f s() in init (), so
we no lo nger need to call addT o List () fro m addIt e m (). We just remo ved that call (o therwise, items we add to Lo cal
Sto rage will be sho wn twice o n the page!).
Let's step thro ugh sho wAllPre f s(), because we're using a co uple o f new things abo ut the lo calSt o rage o bject we
haven't talked abo ut yet: the le ngt h pro perty and the ke y() metho d.
OBSERVE:
function showAllPrefs() {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
var value = localStorage.getItem(key);
addToList(key, value);
}
}
First, remember that the lo calSt o rage o bject uses an associative array to sto re items in the bro wser's Lo cal Sto rage.
While an asso ciative array do esn't have numerical indices like a regular array do es, it do es have a le ngt h pro perty,
which is just the number o f key/value pairs in lo calSt o rage .
We can lo o p thro ugh all the keys in lo calSt o rage and access each key using the ke y metho d. Even tho ugh
lo calSt o rage do esn't have an index like regular arrays, the ke y metho d makes it act a bit like it do es, because the
ke y metho d takes a number and returns a key.
lo calSt o rage .ke y(i) returns the string value o f a key, like "bo o k" o r "bro wserWidth." Once yo u have that key, stashed
in the variable ke y, yo u can use it to re t rie ve t he value asso ciat e d wit h t hat ke y, using
lo calSt o rage .ge t It e m (ke y).
Finally, we pass bo th the ke y and the value to addT o List () to add them to the page.
Yo u're go ing to find that yo u'll frequently use this technique o f iterating thro ugh all the keys in Lo cal Sto rage to retrieve
the values because yo u wo n't always kno w precisely what keys yo u have o r ho w many yo u have.
function init() {
//addItem("favGenre", "fiction");
//addItem("favFlavor", "Vanilla Chocolate Chip");
//addItem("book", "Head First HTML5 Programming");
//addItem("browserWidth", 1280);
//addItem("favTea", "English Breakfast");
removeItem("favFlavor");
showAllPrefs();
}
Save it. Befo re yo u preview, check the items in yo ur Lo cal Sto rage using the bro wser develo per to o ls. Then o pen
pre f s.ht m l and click . If yo u check yo ur Lo cal Sto rage, the item with the key "favFlavo r" sho uld be go ne.
Try remo ving a co uple mo re items fro m Lo cal Sto rage by changing the key yo u pass to re m o ve It e m (), and relo ading
the page. What happens if yo u try to remo ve a key that do esn't exist?
Our re m o ve It e m () functio n uses the lo calSt o rage .re m o ve It e m () metho d to remo ve the item fro m Lo cal Sto rage.
The metho d takes o ne argument, the key to be deleted, and remo ves the key and its value fro m Lo cal Sto rage.
To remo ve all the items in Lo cal Sto rage, use the lo calSt o rage .cle ar() metho d:
CODE TO TYPE:
window.onload = init;
function init() {
//addItem("favGenre", "fiction");
//addItem("favFlavor", "Vanilla Chocolate Chip");
//addItem("book", "Head First HTML5 Programming");
//addItem("browserWidth", 1280);
//addItem("favTea", "English Breakfast");
removeItem("favFlavor");
clearAllItems();
showAllPrefs();
}
Save it, o pen pre f s.ht m l, and click . No w there are no items in yo ur list, and if yo u check yo ur Lo cal
Sto rage using the bro wser's develo per to o l, yo u'll see no items in yo ur Lo cal Sto rage.
As yo u can see, a co o kie has o ne o r mo re keys and o ne o r mo re values asso ciated with tho se keys. So in that way, a
co o kie is quite similar to Lo cal Sto rage. Applicatio ns that interact with a web server use co o kies to sto re tempo rary
data o r perso nalize an experience, in much the same way yo u use Lo cal Sto rage to do tho se things.
Co o kies are sent back and fo rth to and fro m the server each time yo u make a request! So if yo ur applicatio n
co mmunicates with a web server at o reillystudent.co m, every co o kie asso ciated with o reillystudent.co m will
be sent to the server, and retrieved fro m the server, with each request. This adds o verhead to each request
(slo wing do wn yo ur applicatio n).
Co o kies are limited to 4K o f data. Co mpare this to 5MB o f data that each do main gets to sto re data in Lo cal
Sto rage. That's a huge difference!
The JavaScript interface fo r interacting with co o kies is a lo t mo re difficult to use than the lo calSto rage o bject.
It's o ften mo re difficult fo r users to have visibility into what co o kies are sto red o n their co mputers.
Co o kies are still useful fo r certain types o f applicatio ns, but it's likely that Lo cal Sto rage will take o ver a lo t o f the
functio ns that co o kies have been used fo r until no w.
We ho pe yo u've had so me fun sto ring, retrieving, and remo ving items to and fro m yo ur bro wser's Lo cal Sto rage using the
JavaScript lo calSt o rage o bject.
Two mo re things to kno w abo ut Lo cal Sto rage: first, Lo cal Sto rage is part o f the We b St o rage part o f the HTML specificatio n.
Mo st develo pers, ho wever, just call it "Lo cal Sto rage" rather than "Web Sto rage." But if yo u hear "Web Sto rage," it's likely to be
referring to the Lo cal Sto rage feature.
Alo ng with the lo calSt o rage o bject, there is ano ther built-in o bject yo u can use named se ssio nSt o rage . Sessio n Sto rage is
just like Lo cal Sto rage except that all data that is sto red in the bro wser go es away when yo u clo se the tab o r windo w yo u've
been using to interact with applicatio n. Sessio n Sto rage is useful fo r sto ring very tempo rary items that yo u want to make sure
go away when the user leaves the page.
In the next lesso n, we'll update the To do List Applicatio n to use Lo cal Sto rage.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Updating the To-Do List Application for Local Storage
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
Yo u kno w ho w to sto re simple strings in Lo cal Sto rage, and yo u kno w that yo u can sto re o nly strings in Lo cal Sto rage
(meaning yo u can't sto re numbers, o bjects, o r o ther types in Lo cal Sto rage). A to -do item is an o bject. Here's an
example o f o ne:
OBSERVE:
{
task: "get milk",
who: "Scott",
dueDate: "today",
done: false
}
Kno wing that yo u can o nly sto re strings in Lo cal Sto rage, can yo u think o f ho w we might sto re to -do items? And what
sho uld we use as the keys fo r the to -do items?
We can use JSON to sto re o bjects such as o ur to -do items in Lo cal Sto rage. Recall that JSON allo ws us to represent
JavaScript o bjects as strings. Once we have a string representatio n o f an o bject, we can sto re it in Lo cal Sto rage just
like any o ther string. Similarly, we can use JSON to retrieve the string fro m Lo cal Sto rage and turn it back into an o bject.
Befo re we dive back into the To -Do List applicatio n, let's create a small example to sto re and retrieve JSON o bjects
fro m Lo cal Sto rage. Create a new HTML file as sho wn:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Store an Object in Local Storage</title>
<meta charset="utf-8">
<script src="storeObj.js"></script>
</head>
<body>
</body>
</html>
Save this file in yo ur /javascript 2 fo lder as st o re Obj.ht m l. We link to the file st o re Obj.js in the header—that's
where all the real actio n is! No w we need so me HTML so we can preview and lo o k Lo cal Sto rage using the bro wser
develo per co nso le.
Create a new file and add the JavaScript as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var myObject = {
name: "Trish",
age: 29,
favColor: "blue"
};
var myObjectJson = JSON.stringify(myObject);
localStorage.setItem("trish", myObjectJson);
var newMyObjectJSON = localStorage.getItem("trish");
var newMyObject = JSON.parse(newMyObjectJSON);
alert(newMyObject.name + " is " + newMyObject.age +
" and her favorite color is " + newMyObject.favColor);
}
Save this file in yo ur /javascript 2 fo lder as st o re Obj.js, o pen yo ur st o re Obj.ht m l file, click . Yo u
see an alert like this:
Use yo ur bro wser's develo per co nso le to inspect Lo cal Sto rage. The Lo cal Sto rage includes the item yo u just sto red
using this pro gram:
Yo u might see additio nal items in Lo cal Sto rage if yo u've added anything to the Lo cal Sto rage at the
Note o reillystudent.co m do main. Just make sure yo u see the "trish" o bject yo u just added!
OBSERVE:
function init() {
var myObject = {
name: "Trish",
age: 29,
favColor: "blue"
};
var myObjectJson = JSON.stringify(myObject);
localStorage.setItem("trish", myObjectJson);
var newMyObjectJSON = localStorage.getItem("trish");
var newMyObject = JSON.parse(newMyObjectJSON);
alert(newMyObject.name + " is " + newMyObject.age +
" and her favorite color is " + newMyObject.favColor);
}
So no w that yo u kno w ho w to sto re and retrieve an o bject fro m Lo cal Sto rage, let's go back to the To -Do List
applicatio n and mo dify it to use Lo cal Sto rage instead o f Ajax to sto re to -do items.
Fo r no w, let's create a key fo r each to -do item fro m a unique id and a string that represents the applicatio n. We'll create
the unique id fo r each to -do item as that item is created (that is, when yo u submit a new to -do item using the To -Do
List fo rm), sto re that id (alo ng with the o ther to -do list info rmatio n) in the To do o bject, and then use that id when we
create the key to sto re the item in Lo cal Sto rage. Let's update the co de and then we'll go o ver each change.
We'll co ntinue to update the t o do .ht m l and t o do .js files. Yo u might want to make co pies o f the files to
Note save yo ur previo us wo rk.
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoData();
}
function getTodoData() {
var request = new XMLHttpRequest();
request.open("GET", "todo.json");
request.onreadystatechange = function() {
if (this.readyState == this.DONE && this.status == 200) {
if (this.responseText) {
parseTodoItems(this.responseText);
addTodosToPage();
}
else {
console.log("Error: Data is empty");
}
}
};
request.send();
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
var listFragment = document.createDocumentFragment();
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = createNewTodo(todoItem);
listFragment.appendChild(li);
}
ul.appendChild(listFragment);
}
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = createNewTodo(todoItem);
ul.appendChild(li);
document.forms[0].reset();
}
function createNewTodo(todoItem) {
var li = document.createElement("li");
var spanTodo = document.createElement("span");
spanTodo.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
li.appendChild(spanDone);
li.appendChild(spanTodo);
return li;
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
var id = todos.length;
var todoItem = new Todo(id, task, who, date);
todos.push(todoItem);
addTodoToPage(todoItem);
saveTodoData();
saveTodoItem(todoItem);
}
function saveTodoItem(todoItem) {
if (localStorage) {
var key = "todo" + todoItem.id;
var item = JSON.stringify(todoItem);
localStorage.setItem(key, item);
}
else {
console.log("Error: you don't have localStorage!");
}
}
function saveTodoData() {
var todoJSON = JSON.stringify(todos);
var request = new XMLHttpRequest();
var URL = "save.php?data=" + encodeURI(todoJSON);
request.open("GET", URL);
request.setRequestHeader("Content-Type",
"text/plain;charset=UTF-8");
request.send();
}
Save it. We remo ved the functio ns to save and get the to -do items (save T o do Dat a() and (ge t T o do Dat a()) fro m
the server. We replaced the functio n to save the data with a new functio n, save T o do It e m (), which takes o ne to -do
item and saves it to Lo cal Sto rage.
Because we are still using the same JSON fo rmat we used befo re (in the Ajax versio n), we do n't have to change much
o f the o ther co de. That's co nvenient. Befo re we walk thro ugh the details, o pen t o do .ht m l and click . Try
adding a few to -do items using the fo rm:
When yo u lo o k at Lo cal Sto rage using yo ur bro wser, yo u see the new items yo u added. If yo u had o ther items in Lo cal
Sto rage already, the new to -do items will be mixed in with tho se o ther items. Yo u'll be able to tell which items belo ng
to the To -Do List applicatio n because the keys all begin with "to do ."
Right no w we are saving the items we enter using the fo rm in Lo cal Sto rage (we're no t retrieving tho se items when we
first lo ad the page), so yo u'll see new items being added to Lo cal Sto rage and to the page as yo u add them using the
fo rm. If yo u relo ad the page, yo u wo n't see tho se items in yo ur list yet. We'll get to that sho rtly.
Once we have this new id pro perty, we need to update the co de where we create the T o do o bject, to pass in
a unique id. But what do we use fo r that unique id?
OBSERVE:
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
var id = todos.length;
var todoItem = new Todo(id, task, who, date);
todos.push(todoItem);
addTodoToPage(todoItem);
saveTodoItem(todoItem);
}
Each time we create a new T o do o bject representating a to -do item, we're adding that item to the t o do s
array. Each time we add a new item to the array, the length o f the array increases. Because o f that, we can use
the curre nt le ngt h o f t he array as a unique id. We retrieve the length o f the array with t o do s.le ngt h.
Once we have an id, which in this case is just a number, we can use that id when we cre at e a ne w T o do
o bje ct . Next, we add t he ne w T o do it e m t o t he array, and t o t he page , and then save t he ne w it e m
using o ur new functio n save T o do It e m ():
OBSERVE:
function saveTodoItem(todoItem) {
if (localStorage) {
var key = "todo" + todoItem.id;
var item = JSON.stringify(todoItem);
localStorage.setItem(key, item);
}
else {
console.log("Error: you don't have localStorage!");
}
}
If the user's bro wser do esn't have lo calSto rage, we sho w an erro r message in the co nso le. (In a mo re ro bust
applicatio n yo u might want to have ano ther o ptio n.)
Getting T o-Do Items from Local Storage
Okay, we've go t to -do items sto red in Lo cal Sto rage and they're being displayed o n the page as yo u add them. We put
the items in Lo cal Sto rage so they'll be there when yo u relo ad the page (o r want to access yo ur items the next day o r
even a mo nth fro m no w).
We can use lo calSt o rage .ge t It e m () to retrieve items fro m Lo cal Sto rage, but we want to retrieve o nly items with
keys that co ntain the string "to do ." Because we get just the items fro m Lo cal Sto rage when we lo ad the page, we need
to add all the items in Lo cal Sto rage to the list yo u see o n the page as well. We already have a functio n that do es that:
addT o do sT o Page (). It adds all the items in the t o do s array to the page. So , as we retrieve each item fro m Lo cal
Sto rage, we'll add it to the t o do s array, then we can call addT o do sT o Page () to add them to the page.
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoItems();
}
function getTodoItems() {
if (localStorage) {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.substring(0, 4) == "todo") {
var item = localStorage.getItem(key);
var todoItem = JSON.parse(item);
todos.push(todoItem);
}
}
addTodosToPage();
}
else {
console.log("Error: you don't have localStorage!");
}
}
function parseTodoItems(todoJSON) {
if (todoJSON == null || todoJSON.trim() == "") {
return;
}
var todoArray = JSON.parse(todoJSON);
if (todoArray.length == 0) {
console.log("Error: the to-do list array is empty!");
return;
}
for (var i = 0; i < todoArray.length; i++) {
var todoItem = todoArray[i];
todos.push(todoItem);
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
var listFragment = document.createDocumentFragment();
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = createNewTodo(todoItem);
listFragment.appendChild(li);
}
ul.appendChild(listFragment);
}
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = createNewTodo(todoItem);
ul.appendChild(li);
document.forms[0].reset();
}
function createNewTodo(todoItem) {
var li = document.createElement("li");
var spanTodo = document.createElement("span");
spanTodo.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
li.appendChild(spanDone);
li.appendChild(spanTodo);
return li;
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
var id = todos.length;
var todoItem = new Todo(id, task, who, date);
todos.push(todoItem);
addTodoToPage(todoItem);
saveTodoItem(todoItem);
}
function saveTodoItem(todoItem) {
if (localStorage) {
var key = "todo" + todoItem.id;
var item = JSON.stringify(todoItem);
localStorage.setItem(key, item);
}
else {
console.log("Error: you don't have localStorage!");
}
}
Save it, o pen t o do .ht m l, and click . Yo u see all the items that yo u had previo usly added in the page;
yo u'll be able to add new o nes. Give it a try. Remo ve so me items fro m Lo cal Sto rage using the bro wser co nso le to o l,
and then relo ad and make sure that what yo u see in Lo cal Sto rage matches what yo u see in the page.
Yo u may need to refresh the view o f Lo cal Sto rage in yo ur bro wser (using the bro wser to o ls) o r use the
Note JavaScript co nso le to display the lo calSt o rage o bject after yo u add an item to see the updated values.
Let's walk thro ugh the changes we made. We add a functio n ge t T o do It e m s(), and call that functio n fro m the init ()
functio n, so it runs as so o n as the page lo ads and yo u see all the items yo u have sto red, as so o n as yo u lo ad the
page.
OBSERVE:
function getTodoItems() {
if (localStorage) {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.substring(0, 4) == "todo") {
var item = localStorage.getItem(key);
var todoItem = JSON.parse(item);
todos.push(todoItem);
}
}
addTodosToPage();
}
else {
console.log("Error: you don't have localStorage!");
}
}
In ge t T o do It e m s(), we check to make sure the lo calSt o rage o bject o bject exists— if it do esn't, display a message
in the co nso le.
Next, we lo o p thro ugh all the items in Lo cal Sto rage using the le ngt h pro perty o f the lo calSt o rage o bject, but this
time, instead o f adding every item to the page, we want to add o nly the items that have the string "to do " in the key. We
che ck t o m ake sure t hat t he ke y st ring co nt ains t he st ring " t o do " , using the String metho d subst ring()
(we'll co me back to this metho d sho rtly). If it do es, then we get the item fro m Lo cal Sto rage, and use J SON t o parse ()
the string back into a To do o bject, which we sto re in the variable t o do It e m . Then we add t he t o do It e m t o t he
t o do s array. Finally, after we've added all o f the to -do items in Lo cal Sto rage to the t o do s array, we call the
addT o do sT o Page () functio n, which adds all the to -do items to the page.
We delete the parse T o do It e m s() functio n fro m the co de. We do n't need this functio n any mo re because we parse
e ach it e m in t he lo o p as we get the items fro m Lo cal Sto rage, and we add e ach it e m t o t he array right there.
Remember, we used parse T o do It e m s() in the Ajax versio n o f the To -Do List applicatio n to parse the JSON we go t
back fro m the XMLHttpRequest.
If yo u have no "to do " items in Lo cal Sto rage, when yo u lo ad yo ur page, yo u'll see:
After adding so me "to do " items, yo u'll see:
T he String substring() method
In the functio n we just added, ge t T o do It e m s(), we used a String metho d, subst ring(). We haven't talked much
abo ut String metho ds yet; we have a who le lesso n devo ted to Strings later in this co urse. Fo r no w, let's take a clo ser
lo o k at the subst ring() metho d to see ho w it wo rks, and ho w to use it to pull o ut o nly to -do items fo r the To -Do List
applicatio n.
Think o f a String as a set o f multiple characters. It's a little bit like an array (but it's definitely NOT an array, so do n't
co nfuse the two ), in that a character in a string ho lds a po sitio n, like this:
So the string "to do 12" has a length o f six; six separate characters that to gether make up the String. We capitalize
St ring here because St ring is a special o bject in JavaScript. It's no t quite like the o bjects yo u create in JavaScript, but
similar in the sense that a String has pro perties (such as le ngt h) and metho ds (such as subst ring()).
The subst ring() metho d allo ws yo u to take any String and create a new String fro m it by using a range o f characters.
The two arguments yo u pass to the subst ring() metho d are a f ro m value and a t o value, where f ro m is the po sitio n
o f the beginning character yo u want and t o is the po sitio n o f the next character after the ending character yo u want. Fo r
example, if yo u have the string "Hello Wo rld!" and yo u want to get the string "lo w", yo ur co de wo uld lo o k like this:
OBSERVE:
var str="Hello world!";
var mySubString = str.substring(3,7);
The variable m ySubSt ring will co ntain "lo w": all the characters beginning at po sitio n 3 and ending at (but not
including the character at) po sitio n 7. (Remember that string po sitio ns start at 0 , so "H" is at 0 , "e" is at 1, and so o n).
Fo r o ur To do applicatio n, we can extract the "to do " po rtio n o f the key items using subst ring(), like this:
Yo u can use the subst ring() metho d o n any string yo u create in JavaScript. So , if we have a variable ke y that
co ntains the string "to do 12," we can call subst ring() o n the ke y variable—ke y.subst ring(0 , 4 )—which returns the
first fo ur characters o f the string (fro m po sitio ns 0 , 1, 2, and 3), unless the length o f the value in the key is less than fo ur
characters. In that case, yo u get all the letters in the string—so if the ke y co ntains the string "to ," yo u get "to " as the
result.
We can co mpare the result o f calling subst ring() o n ke y to a literal string, "to do ," to see if they are equal, using the
== o perato r:
OBSERVE:
if (key.substring(0, 4) == "todo") {
...
}
If they are, we kno w we've fo und a key fo r a to -do item. Because the subst ring() metho d returns a new string, we
haven't changed the value o f ke y at all. In o ur example, ke y still co ntains the string "to do 12."
In this lesso n, we updated the To -Do List applicatio n to use Lo cal Sto rage instead o f Ajax. That means the user's to -do items
are sto red in the bro wser they used to add items to their to -do list, using the To -Do List applicatio n.
We also learned the impo rtance o f cho o sing go o d keys fo r yo ur applicatio ns that use Lo cal Sto rage. In this example, we used
the string "to do " and a unique id to create a key fo r to -do list items. It seems to be wo rking well, but can yo u think o f anything
that might cause o ur id / key scheme to fail? What wo uld happen if yo u deleted a to do item fro m Lo cal Sto rage, and then added
a new item using the fo rm witho ut relo ading the page first? Will the ids always match the po sitio n in the to do s array when yo u
get all the items fro m Lo cal Sto rage, when yo u first lo ad the page, using the functio n ge t T o do It e m s()? If no t, co uld that cause
a pro blem?
Think abo ut tho se questio ns in preparatio n fo r the next lesso n, take a crack at the quiz and pro ject, and we'll see yo u in the next
lesso n!
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Deleting To-Do List Items
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
We've made a lo t o f pro gress with the To -Do List applicatio n, but wo uldn't it be nice if we co uld delete items as well as add
them? We also need a way to mark them as do ne. We'll add that functio nality in this lesso n and in the pro ject later. Let's get
started.
We generate the HTML to represent a to -do item in the web page in the functio n cre at e Ne wT o do (), so we'll update
that co de to add the delete butto n. We'll also add a functio n to handle the click o n the delete butto n. This functio n is a
click handler that will eventually implement the delete functio nality; fo r no w we'll just display so me info rmatio n in the
co nso le abo ut the click.
We'll co ntinue to update the t o do .ht m l and t o do .js files. Yo u might want to make co pies o f the files to
Note save yo ur previo us wo rk.
window.onload = init;
function init() {
var submitButton = document.getElementById("submit");
submitButton.onclick = getFormData;
getTodoItems();
}
function getTodoItems() {
if (localStorage) {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.substring(0, 4) == "todo") {
var item = localStorage.getItem(key);
var todoItem = JSON.parse(item);
todos.push(todoItem);
}
}
addTodosToPage();
}
else {
console.log("Error: you don't have localStorage!");
}
}
function addTodosToPage() {
var ul = document.getElementById("todoList");
var listFragment = document.createDocumentFragment();
for (var i = 0; i < todos.length; i++) {
var todoItem = todos[i];
var li = createNewTodo(todoItem);
listFragment.appendChild(li);
}
ul.appendChild(listFragment);
}
function addTodoToPage(todoItem) {
var ul = document.getElementById("todoList");
var li = createNewTodo(todoItem);
ul.appendChild(li);
document.forms[0].reset();
}
function createNewTodo(todoItem) {
var li = document.createElement("li");
var spanTodo = document.createElement("span");
spanTodo.innerHTML =
todoItem.who + " needs to " + todoItem.task + " by " + todoItem.dueDate;
spanDelete.onclick = deleteItem;
li.appendChild(spanDone);
li.appendChild(spanTodo);
li.appendChild(spanDelete);
return li;
}
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
var id = todos.length;
var todoItem = new Todo(id, task, who, date);
todos.push(todoItem);
addTodoToPage(todoItem);
saveTodoItem(todoItem);
}
function saveTodoItem(todoItem) {
if (localStorage) {
var key = "todo" + todoItem.id;
var item = JSON.stringify(todoItem);
localStorage.setItem(key, item);
}
else {
console.log("Error: you don't have localStorage!");
}
}
function deleteItem(e) {
var id = e.target.id;
console.log("delete an item: " + id);
}
Save it, but do n't preview yet; we need to add the CSS. Let's go ahead and do that, and then we'll co me back to the
co de to step thro ugh it in detail.
CODE TO TYPE:
body {
font-family: Helvetica, Ariel, sans-serif;
}
legend {
font-weight: bold;
}
div.tableContainer {
display: table;
border-spacing: 5px;
}
div.tableRow {
display: table-row;
}
div.tableRow label {
display: table-cell;
text-align: right;
}
div.tableRow input {
display: table-cell;
}
ul#todoList {
list-style-type: none;
margin-left: 0px;
padding-left: 0px;
}
ul#todoList li {
position: relative;
padding: 5px;
margin: 5px;
background-color: #ededed;
border: 2px inset #ededed;
}
ul#todoList li span.notDone {
margin-right: 10px;
background-color: #FF9999;
cursor: pointer;
}
ul#todoList li span.done {
margin-right: 10px;
background-color: #80CC80;
cursor: pointer;
}
ul#todoList li span.delete {
display: inline-block;
position: absolute;
right: 5px;
cursor: pointer;
}
Save it, o pen t o do .ht m l, and click . Yo u'll see a delete "butto n" next to each to -do item in the page.
In the JavaScript, we update the cre at e Ne wT o do () functio n to generate HTML to represent the delete "butto n" (which
isn't a butto n in the sense o f a fo rm butto n, but rather an element yo u can click o n to take so me actio n):
OBSERVE:
var spanDelete = document.createElement("span");
spanDelete.setAttribute("id", todoItem.id);
spanDelete.setAttribute("class", "delete");
spanDelete.innerHTML = " ✗ ";
We cre at e ano t he r <span> e le m e nt just like we did fo r the do ne/no t do ne butto n, and use t he e nt it y &# 10 0 0 7 ;
t o ge ne rat e t he charact e r.
As we did fo r the do ne/no t do ne butto n, we se t t he " class" at t ribut e o f t he <span> e le m e nt , in t his case , t o
" de le t e " , so we can style it (we'll get to the style in just a mo ment).
No tice that we also se t t he " id" at t ribut e o f t he <span> e le m e nt ; that's so we kno w which element we've
clicked o n when we implement the de le t e It e m () functio n (yo u'll see ho w this wo rks a bit later). The id o f the item we
want to delete is just the t o do It e m .id pro perty. So if an item has the id 1, the <span> element will lo o k like this:
OBSERVE:
<span id="1" class="delete">✗</span>
No w, the list item representing the to -do item has three <span> elements in it. The generated HTML will lo o k
so mething like this:
OBSERVE:
<li>
<span class="notDone"> </span>
<span>TEXT OF TO DO ITEM</span>
<span id="1" class="delete">✗</span>
</li>
To style the delete butto n so it sits all the way to the right o f the to -do item, we po sitio n it using CSS po sitio ning:
OBSERVE:
ul#todoList li {
position: relative;
padding: 5px;
margin: 5px;
background-color: #ededed;
border: 2px inset #ededed;
}
ul#todoList li span.notDone {
margin-right: 10px;
background-color: #FF9999;
cursor: pointer;
}
ul#todoList li span.done {
margin-right: 10px;
background-color: #80CC80;
cursor: pointer;
}
ul#todoList li span.delete {
display: inline-block;
position: absolute;
right: 5px;
cursor: pointer;
}
We can po sitio n the delete butto n using abso lut e po sit io ning, which allo ws us to specify the number o f pixels fro m
the right o f the list item that we want the delete butto n to appear. We used 5 px to leave a little space between the
butto n and the edge o f the list item.
But, to po sitio n an element abso lutely relative to ano ther element (rather than the page), we need to po sitio n its parent
element as well. The parent element o f the <span> that represents the delete butto n is the <li> element. We can
po sitio n the <li> element so that it go es with the flo w o f the page using po sit io n: re lat ive . So the <li> element is
po sit io ne d re lat ive to where it wo uld be no rmally, and then the delete <span> is po sit io ne d abso lut e ly within
the <li> element, 5 px fro m the right.
Finally, we added a pro pe rt y that changes the curso r t o a po int e r. No w when yo u mo use o ver the do ne/no t do ne
butto n o r the delete butto n, yo u'll see a hand po inter ( ) rather than the regular mo use po inter, which pro vides visual
feedback that indicates that yo u're ho vering o ver so mething o n which yo u can click.
OBSERVE:
var spanDelete = document.createElement("span");
spanDelete.setAttribute("id", todoItem.id);
spanDelete.setAttribute("class", "delete");
spanDelete.innerHTML = " ✗ ";
spanDelete.onclick = deleteItem;
After creating the <span> element that ho lds the delete butto n, we add a click handle r t o it by setting the
o nclick pro perty to the functio n de le t e It e m (). Fo r no w, the de le t e It e m () functio n is pretty straightfo rward:
OBSERVE:
function deleteItem(e) {
var id = e.target.id;
console.log("delete an item: " + id);
}
The de le t e It e m () functio n has o ne parameter, e , that is the click e ve nt data. When yo u click o n an element
with a click handler, the event data is always passed into the click handler functio n; it co ntains data abo ut the
click, including the t arge t , which is the e le m e nt t hat was clicke d o n. In o ur case, that element is the
"delete" <span>. We kno w that the <span> element has an id attribute (because we added it!) set to the id o f
the to -do item, so we can access that id using the id pro perty o f the <span> element that is sto red in the
t arge t pro perty.
We co uld also access the id attribute using the ge t At t ribut e () metho d, like this:
Note var id = e .t arge t .ge t At t ribut e (" id" );.
Once we have the id o f the to -do item we want to delete, we display the id to the co nso le so we can verify that
it's co rrect.
T ry it !
t o do .ht m l again, o pen the Develo per to o ls fo r yo ur bro wser so yo u can see the JavaScript
co nso le, and try deleting a to -do item. It do esn't go away, because we haven't implemented de le t e It e m ()
yet, but the message in the co nso le will indicate which item was clicked. Try clicking o n several different items.
Make sure the ids yo u see in the co nso le are co rrect by co mparing them with the ids yo u see in Lo cal
Sto rage.
First, we added the HTML fo r the delete "butto n" to the HTML, and set the id attribute o f the <span> element to
the id o f the to -do item:
We also added a click handler so that when yo u click o n the delete butto n, the click handler is called. The
target o f the event that's passed to the click handler is the element yo u clicked o n, which is the <span>
representing the delete butto n. Yo u can get the id o f the to -do item to delete fro m the id o f the <span>
element:
That id sho uld match the id o f the element in Lo cal Sto rage:
We're almost ready to implement de le t e It e m ().
Remember that we add all to -do items to the t o do s array, when we first lo ad the page (items already in Lo cal
Sto rage), and also as we add new items using the fo rm. We use the length o f the t o do s array to create a unique id fo r
each T o do o bject that we create, like this (in the ge t Fo rm Dat a() functio n):
OBSERVE:
var id = todos.length;
...so that the id o f the T o do matches the index o f the item in the t o do s array:
When we delete an item, we'll delete it fro m three places: Lo cal Sto rage, the web page, and the t o do s array. Let's say
we decide to delete the item with id 1. When we delete it fro m the array, the o ther items in the array shift do wn, so no w
the T o do with id 2 is in the array at index 1, the T o do with id 3 is in the array at index 2, and the length o f the array is
reduced by o ne; in this example, the length is no w 3 (befo re the delete, the length was 4):
The tro uble arises when we go to add a new T o do . Because the length o f the array is 3, the id o f the new item is set to
3, but we already have an item with the id 3. Even so , we add it to the array with no pro blem; when we add it to Lo cal
Sto rage, we create the key to sto re it in Lo cal Sto rage using the T o do item's id and the string "to do ", getting "to do 3" in
this example. Ho wever, there is already a "to do 3" key with a value in Lo cal Sto rage, and yo u kno w when happens
when yo u use the same key to sto re a new value in Lo cal Sto rage. Yup, yo u overwrite the existing value, which means
yo u just lo st a to -do item o n yo ur list!
So , we need ano ther way to create unique ids fo r o ur T o do items so that we have unique keys fo r each item in Lo cal
Sto rage. Can yo u think o f ho w we might do that?
Yo u might think o f this as an o dd cho ice fo r creating a unique id, but it turns o ut to wo rk well o n to day's co mputers, and
using JavaScript in particular, because we can get a value fo r time that is t he num be r o f m illise co nds since
0 1/0 1/19 7 0 . Why 19 70 ? That's just a date that was picked a lo ng time ago by co mputer scientists and we must accept
it. The JavaScript Dat e o bject's ge t T im e () metho d still uses it (and this technique is used by many co mputers and
many co mputer languages, so yo u'll see it po p up in o ther types o f applicatio ns to o ).
Let's try an example. Go to the JavaScript co nso le in a web page and type this:
CODE TO TYPE:
var d = new Date();
var m = d.getTime();
console.log(m);
The co de ne w Dat e () creates a new Dat e o bject that represents "right no w." (Try typing co nso le .lo g(d) to see). We
co nvert that date into milliseco nds using the ge t T im e () metho d. We'll talk mo re abo ut the Dat e o bject and the Dat e
o bject co nstructo r later in the co urse.
We can co mbine the two lines to create a Dat e o bject and get the time with the ge t T im e () metho d into o ne, like this:
CODE TO TYPE:
(new Date()).getTime();
Try that in yo ur co nso le and make sure it wo rks. Make sure yo u put the parentheses in the right places! The
parentheses ensure that we get the Dat e o bject fo r "right no w" first, and then call ge t T im e () o n that o bject. Yo u'll see
this syntax used o ften in JavaScript, bo th with the Dat e o bject and o ther o bjects to o .
Okay, no w that we have a new scheme fo r o ur ids, let's add it to o ur co de. We just have to change o ne line in the
functio n ge t Fo rm Dat a() in o ur t o do .js file:
CODE TO TYPE:
function getFormData() {
var task = document.getElementById("task").value;
if (checkInputText(task, "Please enter a task")) return;
var id = todos.length;
var id = (new Date()).getTime();
var todoItem = new Todo(id, task, who, date);
todos.push(todoItem);
addTodoToPage(todoItem);
saveTodoItem(todoItem);
}
Save it, o pen t o do .ht m l, and click . Make sure yo u have yo ur Develo per co nso le o pen and display the
co ntents o f Lo cal Sto rage, either using the JavaScript co nso le, o r using the Reso urces tab (depending o n yo ur
bro wser). If yo u have any existing to -do items, tho se will have the o ld ids. That's fine. Try adding so me new o nes;
refresh yo ur view o f Lo cal Sto rage and make sure they have new ids that use the time in milliseco nds.
Click o n an item that has o ne o f the new ids. In the JavaScript co nso le, yo u'll see the co nso le.lo g message that sho ws
the id o f the item yo u want to delete, as well as the big, lo ng id.
Okay, no w that we have a new id, we're really ready to implement de le t e It e m (), so let's get to it!
Save it, o pen t o do .ht m l, and click . Try adding and deleting so me items. Check yo ur Lo cal Sto rage
using the develo per co nso le and make sure the items are go ne. Relo ad the page and make sure, again, that they are
go ne.
Let's walk thro ugh the co de. First, we remo ve the to -do item fro m Lo cal Sto rage. We use the lo calSt o rage metho d,
re m o ve It e m () and pass in the ke y t o de le t e . The key is a string created by co m bining " t o do " wit h t he id, just
like we did when we created the key to add the item to Lo cal Sto rage. Remember, we get id fro m the <span> element
that we click o n to delete an item:
OBSERVE:
// find and remove the item in localStorage
var key = "todo" + id;
localStorage.removeItem(key);
OBSERVE:
To delete a to -do item fro m the t o do s array, we lo o p t hro ugh t he array, lo o king fo r the T o do o bject with the same
id as the id o f the item o n which we clicked. If we f ind t he T o do wit h t hat id, then we can remo ve it fro m the array
using the array metho d, splice (). We pass in the po sit io n o f t he it e m we 're de le t ing (i), and the num be r o f
it e m s t o de le t e (in t his case , just o ne ), then splice () remo ves that item fro m the array, and shifts everything else
in the array do wn by o ne so that there's no empty spo t in the array. Yo u can use splice () to remo ve multiple elements
fro m an array, but in this case, we just need to delete the o ne to -do item with the id that matches the o ne we clicked o n
to delete.
If we've f o und t he it e m t o de le t e , we do n't need to keep lo o ping. It wo uld be a waste o f time to lo o p o ver the rest
o f the elements, because we kno w no ne o f the o ther items will match the id we're seeking. So , we can use the bre ak
statement to break o ut o f the lo o p. When yo u use bre ak, the lo o p sto ps, and the co de fo llo wing the f o r lo o p will run
next.
In this case, that next co de is the co de to remo ve the to -do item fro m the page. We've remo ved it fro m Lo cal Sto rage,
and remo ved it fro m the t o do s array, but the item still appears o n the page:
OBSERVE:
To remo ve the item fro m the page, we need to remo ve the <li> e le m e nt t hat re pre se nt s (and displays) t he t o -
do it e m in t he page fro m the DOM. Remember that the DOM is an internal bro wser structure that represents what
yo u see o n the page. It co ntains all the elements and co ntent o f the page.
When yo u click o n the delete <span>, that <span> is passed into the click handler functio n, de le t e It e m (), in the event
o bject, and we access it with the pro perty t arge t . Because the <span> is a child o f the <li> element we want to remo ve,
we can get the <li> element using the pare nt Ele m e nt pro perty o f the <span>. Once we have the right <li> element,
we can remo ve it fro m the DOM by using the re m o ve Child() metho d o f the "to do List" <ul> element that co ntains that
<li> element. Here's ho w it wo rks:
Calling re m o ve Child() and passing in the <li> element immediately remo ves that list item fro m the page, and then
yo u see the to -do item disappear when yo u click delete.
We can't just add ano ther id to that <span> element with the same id we're using fo r the delete butto n, because id
attributes must be unique in HTML. Hmm....
A go o d so lutio n in this case is to mo ve the id up to the parent element: the <li> that co ntains both the do ne/no t <span>
element and the delete <span> element. No w that yo u kno w ho w to access an element's parent element, yo u can get
the id o f the list item to mo dify fro m the parent element, the <li>, rather than the <span>, so that single id will wo rk fo r
bo th the delete click handler functio n, and the do ne/no t do ne click handler functio n that yo u're go ing to write in the
pro ject. Here's ho w that will wo rk:
Let's go ahead and mo ve the id fro m the delete <span> up to the <li> element fo r the to -do item when we create that
element, in cre at e Ne wT o do (). We'll also need to update the de le t e It e m () functio n so that it gets the id fro m the
parent element (the <li>) rather than fro m the target element (the <span>). Mo dify t o do .js as sho wn:
CODE TO TYPE:
.
.
.
function createNewTodo(todoItem) {
var li = document.createElement("li");
li.setAttribute("id", todoItem.id);
//spanDone.onclick = updateDone;
li.appendChild(spanDone);
li.appendChild(spanTodo);
li.appendChild(spanDelete);
return li;
}
function deleteItem(e) {
var id = e.target.id;
var span = e.target;
var id = span.parentElement.id;
console.log("delete an item: " + id);
All we did is change the cre at e Ne wT o do () functio n so that instead o f adding the id attribute to the delete <span>, we
add it to the <li> element. Then in de le t e It e m (), we use the <span> element's pare nt Ele m e nt pro perty to get the id
fro m the <li> element—the rest o f the co de stays the same.
Make sure yo u understand ho w this wo rks because yo u're go ing to need to use it to implement a new click handler,
updat e Do ne () in the pro ject.
Wo w, yo u go t thro ugh a lo t in this lesso n! We implemented the delete butto n and figured o ut ho w to make an id scheme using
time in milliseco nds that wo rks well with o ur applicatio n. We intro duced the Dat e o bject, the Dat e () co nstructo r and the
ge t T im e () metho d briefly; we'll be co ming back to Dat e in a later lesso n.
Take a break, and then tackle the pro ject to implement the do ne/no t do ne functio nality. Once yo u've co mpleted that, yo u'll have
a very nice To -Do List Applicatio n yo u can use!
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Strings and String Methods
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
It's time fo r a well-deserved break fro m the To -Do List Applicatio n. In this lesso n, we'll build a string search applicatio n and, in
the pro cess, explo re several o f the different metho ds we have fo r manipulating strings in JavaScript. Yo u've used strings many
times in this co urse, but we haven't do ne much with them except to put them into web pages. There is a lo t mo re yo u can do
with strings, as yo u'll so o n disco ver.
String Basics
Yo u already kno w that to create a string in JavaScript, yo u write so me text in quo tes, like this:
OBSERVE:
var myString = "I'm a string!";
It's fine fo r a string to co ntain a single quo te, as lo ng as yo u're using do uble quo tes to delimit the string, but if yo u need
a do uble quo te in a string, yo u need to escape it, like this:
OBSERVE:
var myQuoteString = "He said, \"Give me the ice cream!\" but I didn't.";
Strings in JavaScript are a bit special. When yo u write a string, yo u're actually creating a St ring o bje ct . Just like o ther
o bjects, String o bjects have metho ds and pro perties. Yo u already kno w abo ut o ne pro perty, le ngt h. Type this into
yo ur bro wser's JavaScript co nso le:
CODE TO TYPE:
When yo u want to see the value o f a variable in the JavaScript co nso le, yo u do n't actually have to use
Note co nso le .lo g(); yo u can just type the name o f the variable. Fo r example, if yo u want the value o f le n, yo u
can just type le n at the co nso le pro mpt.
If "a" is the fifth character in the string, ho w did charAt() get "a"? It retrieved the character at po sitio n (also called inde x)
4. Just like arrays, strings start with po sitio n 0 , so if yo u co unt 0 , 1, 2, 3, 4 characters (including the space!), yo u'll find
"a" in po sitio n 4. But just because yo u can access a character in a String using a po sitio n, o r index, do n't co nfuse it
with an Array because they are two entirely different o bjects.
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Strings</title>
<meta charset="utf-8">
<script src="strings.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
textarea {
width: 700px;
height: 400px;
}
label {
vertical-align: top;
}
</style>
</head>
<body>
<form>
<label for="searchTerm">Search for: </label>
<input type="text" id="searchTerm" size="35"
placeholder="search term">
<input type="button" id="searchButton" value="Search"><br><br>
<label for="textToSearch">Search this text:</label>
<textarea id="textToSearch"></textarea>
</form>
</body>
</html>
Save the file in yo ur /javascript 2 fo lder as st rings.ht m l and click . Yo u see this:
Save the file in yo ur /javascript 2 fo lder as st rings.ht m l and click . Yo u see this:
No thing wo rks yet because we haven't written the JavaScript. The plan is that when yo u click the Search butto n, we'll
begin the search pro cess and search fo r the string yo u enter in the to p search area within the string in the textarea at
the bo tto m. No tice that we're linking to the file st rings.js fro m the HTML. That's where we'll add the JavaScript to make
this wo rk; let's do that no w.
window.onload = init;
function init() {
var searchButton = document.getElementById("searchButton");
searchButton.onclick = searchText;
}
function searchText() {
var searchTerm = document.getElementById("searchTerm").value;
var textToSearch = document.getElementById("textToSearch").value;
if (searchTerm == null || searchTerm == "") {
alert("Please enter a string to search for");
return;
}
if (textToSearch == null || textToSearch == "") {
alert("Please enter some text to search");
return;
}
if (searchTerm == textToSearch) {
alert("Found 1 instance of " + searchTerm);
}
else {
alert("No instance of " + searchTerm + " found!");
}
}
Save it as st rings.js, o pen st rings.ht m l, and click . Try entering a search string and so me text to
search. Yo u'll pro bably no tice two things right away:
If either o f the strings yo u enter has extra white space at the beginning o r the end, yo u might find yo ur search
do esn't wo rk, even if it appears that the text in bo th bo xes is exactly the same.
Bo th strings must be exactly the same in o rder fo r the search to wo rk, which isn't particularly useful.
Do n't wo rry, we'll fix bo th o f these pro blems. First things first. Try entering "test" in the search area, and " test " in the
text area:
These strings wo n't match when yo u click the Search butto n:
The co de that tests to see if the two strings are the same:
OBSERVE:
if (searchTerm == textToSearch) {
alert("Found 1 instance of " + searchTerm);
}
The == o perato r checks to see if they are exactly the same, spaces included; when co mparing two strings using ==,
JavaScript co mpares the two strings, character by character, to see if they are the same, and that includes any spaces
in the quo tes. "test" do es no t equal " test ", so the message we get is co rrect.
The t rim () metho d remo ves leading and trailing spaces fro m a string. This functio n is handy; adding extra spaces is a
co mmo n erro r and typically peo ple do n't really want tho se extra spaces when they are co mparing strings. (In so me
situatio ns they might, ho wever, so yo u'll need to take this o n a case-by-case basis).
function init() {
var searchButton = document.getElementById("searchButton");
searchButton.onclick = searchText;
}
function searchText() {
var searchTerm = document.getElementById("searchTerm").value;
var textToSearch = document.getElementById("textToSearch").value;
searchTerm = searchTerm.trim();
textToSearch = textToSearch.trim();
if (searchTerm == null || searchTerm == "") {
alert("Please enter a string to search for");
return;
}
if (textToSearch == null || textToSearch == "") {
alert("Please enter some text to search");
return;
}
if (searchTerm == textToSearch) {
alert("Found 1 instance of " + searchTerm);
}
else {
alert("No instance of " + searchTerm + " found!");
}
}
Save the file (st rings.js)), o pen st rings.ht m l and click . No w two strings will match if they have the
same letters, even if yo u use leading o r trailing spaces. Try it! Try "test" and " test " again.
The built-in trim functio n is o nly available in recent versio ns o f mo st bro wsers: Firefo x 3.5+, Safari 5+, IE9 +, Chro me
5+, and Opera 10 .5+. Fo r bro wsers that do n't suppo rt the built-in functio n, yo u can substitute yo ur o wn implementatio n:
OBSERVE:
function trim(str) {
return str.replace(/^\s+|\s+$/g,"");
}
And then instead o f calling m ySt ring.t rim (), yo u'd write t rim (m ySt ring).
The inde xOf () metho d takes a string to find in the string it's called o n, and returns the po sitio n o f the first instance it
finds. So , if yo u write:
OBSERVE:
var myString = "I scream, you scream, we all scream for ice cream";
var pos = myString.indexOf("scream");
pos
...yo u'll get the value 2 in the variable po s, because the f irst inst ance o f " scre am " starts at po sitio n 2 in the
variable m ySt ring. inde xOf () can also take an o ptio nal argument, a starting po sitio n, so that yo u can start lo o king
fo r a string at that po sitio n (if yo u do n't supply this argument, the default is to start lo o king at po sitio n 0 , the beginning
o f the string):
OBSERVE:
var myString = "I scream, you scream, we all scream for ice cream";
var pos = myString.indexOf("scream", 3);
pos
No w yo u'll get 14, the po sitio n o f the f irst inst ance o f " scre am " , st art ing at o r af t e r po sit io n 3.
If inde xOf () do esn't find any instances o f the string yo u pass it, then it returns -1.
The functio n t o Uppe rCase () co nverts the characters in a string to upper case:
OBSERVE:
var myString = "I scream, you scream, we all scream for ice cream";
var myStringUpper = myString.toUpperCase();
myStringUpper
Our co de pro duces the string "I SCREAM, YOU SCREAM, WE ALL SCREAM FOR ICE CREAM", in the variable
m ySt ringUppe r. There is an analo go us t o Lo we rCase () metho d also . No tice that these metho ds do no t change the
o riginal string, m ySt ring, rather, the metho ds return a new string that yo u can sto re in a different variable if yo u want.
Let's use these two metho ds to impro ve o ur search. Mo dify st rings.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var searchButton = document.getElementById("searchButton");
searchButton.onclick = searchText;
}
function searchText() {
var searchTerm = document.getElementById("searchTerm").value;
var textToSearch = document.getElementById("textToSearch").value;
searchTerm = searchTerm.trim();
textToSearch = textToSearch.trim();
if (searchTerm == null || searchTerm == "") {
alert("Please enter a string to search for");
return;
}
if (textToSearch == null || textToSearch == "") {
alert("Please enter some text to search");
return;
}
if (searchTerm == textToSearch) {
alert("Found 1 instance of " + searchTerm);
}
else {
alert("No instance of " + searchTerm + " found!");
}
var pos = 0;
var count = 0;
while (pos >= 0) {
pos = textToSearch.toUpperCase().indexOf(searchTerm.toUpperCase(), pos);
if (pos >= 0) {
count++;
pos++;
}
}
alert("Found " + count + " instances of " + searchTerm);
}
Save the file, o pen st rings.ht m l and click . Try a few test strings. Make sure yo u try:
lo wer and upper case wo rds
wo rds with spaces in them
wo rds with multiple instances in the text yo u're searching
We pasted in so me text fro m The Adventures o f Sherlo ck Ho lmes, and tried so me searches o n the first paragraph
fro m the bo o k.
Ho w many times do es the wo rd "and" appear in the paragraph? Here's what yo u get if yo u try:
OBSERVE:
var pos = 0;
var count = 0;
while (pos >= 0) {
pos = textToSearch.toUpperCase().indexOf(searchTerm.toUpperCase(), pos);
if (pos >= 0) {
count++;
pos++;
}
}
alert("Found " + count + " instances of " + searchTerm);
Let's break it do wn. We use a lo o p so we can find all instances o f the se archT e rm string that appear in the
t e xt T o Se arch string, and keep track o f ho w many we find using the co unt variable. We use the po s variable to keep
track o f the po sitio n where we find each instance o f se archT e rm . As lo ng as po s is gre at e r t han o r e qual t o 0 we
kno w we've fo und ano ther instance (remember, inde xOf () returns -1 when it can't find an instance o f the string fo r
which yo u're searching). We co nvert bo th strings to upper case with t o Uppe rCase () so that we can find "and" as well
as "AND" o r "And" (o r even "aND"). We use inde xOf () with the seco nd, o ptio nal, argument, po s, so that we can start
searching at a po sitio n that is after the po sitio n where we fo und the previo us instance (so we do n't keep finding the
same instance o ver and o ver). As lo ng as we keep finding a new instance o f the se archT e rm , we increment the
po sitio n, and we increment the co unt. When po s is -1, the lo o p sto ps because we've fo und all the instances o f
se archT e rm there are to find in t e xt T o Se arch.
se archT e rm there are to find in t e xt T o Se arch.
Chaining
We used a JavaScript technique in this co de called chaining. Chaining is co mbining multiple metho d calls
to gether in o ne line o f co de. Fo r instance, if yo u write:
OBSERVE:
var myString = "This is the text we're searching to find the word 'and'.";
var anotherString = "AND";
var pos = myString.toUpperCase().indexOf(anotherString);
OBSERVE:
var myString = "This is the text we're searching to find the word 'and'.";
var anotherString = "AND";
var myStringUpper = myString.toUpperCase()
var pos = myStringUpper.indexOf(anotherString);
By chaining expressio ns to gether with the "do t no tatio n," yo u eliminate the intermediate variable yo u'd create
if yo u used two statements instead. Yo u can do this when yo u kno w that the result o f the first metho d will yield
a value yo u can use fo r the seco nd metho d. In this case, m ySt ring.t o Uppe rCase () returns an uppe r-case
st ring, then we can call the metho d inde xOf () o n t hat st ring.
OBSERVE:
var myString = "I scream, you scream, we all scream for ice cream";
var myStringSub = myString.substring(0, 8);
myStringSub
The value o f m ySt ringSub is "I scream", a string made fro m the characters at po sitio ns 0 -7 o f m ySt ring. No tice that
the character at po sitio n 8 is not included. subst ring() takes two values, f ro m and t o : f ro m is the po sitio n o f the first
character yo u want included in the substring, and t o is one greater than the po sitio n o f the last character yo u want
included in the substring.
The split () metho d is pretty handy. It splits a string into parts using a character as the divider fo r the split. So , let's say
we want to split the text we entered in o ur string search applicatio n into wo rds. We can split the input string using the " "
(space) character. The result o f split () is an array, so in this case, we'd get an array o f all the wo rds in the text. Let's
mo dify o ur string search applicatio n a bit to co unt the number o f wo rds, and then search fo r a wo rd by co mparing the
search text to each wo rd we fo und in the text we searched. Mo dify st rings.js:
CODE TO TYPE:
window.onload = init;
function init() {
var searchButton = document.getElementById("searchButton");
searchButton.onclick = searchText;
}
function searchText() {
var searchTerm = document.getElementById("searchTerm").value;
var textToSearch = document.getElementById("textToSearch").value;
searchTerm = searchTerm.trim();
textToSearch = textToSearch.trim();
if (searchTerm == null || searchTerm == "") {
alert("Please enter a string to search for");
return;
}
if (textToSearch == null || textToSearch == "") {
alert("Please enter some text to search");
return;
}
var pos = 0;
var count = 0;
while (pos >= 0) {
pos = textToSearch.toUpperCase().indexOf(searchTerm.toUpperCase(), pos);
if (pos >= 0) {
count++;
pos++;
}
}
alert("Found " + count + " instances of " + searchTerm);
Save it, o pen st rings.ht m l, and click . Try searching fo r a string. No w yo u'll see an alert that displays
the number o f times the string yo u searched fo r was fo und, as well as ho w many to tal wo rds were fo und:
OBSERVE:
var results = textToSearch.split(" ");
var count = 0;
for (var i = 0; i < results.length; i++) {
if (searchTerm.toUpperCase() == results[i].toUpperCase()) {
count++;
}
}
First, we split t he t e xt T o Se arch into wo rds using split (), and sto red the results in an array nam e d re sult s. Then,
we iterate o ver the entire array, and co m pare e ach wo rd in t he array wit h t he wo rd we 're se arching f o r, and
ke e p t rack o f ho w m any t im e s we f o und it .
If yo u're using the text fro m the Sherlo ck Ho lmes example (abo ve), try the wo rd "and," and yo u might see that "and"
appears 8 times. If yo u tried "and" earlier (when we were using inde xOf () rather than split ()) yo u pro bably saw that it
was fo und 9 times in the text. So why 8 this time? If yo u typed in the text to search o n and pressed the "Enter" key o n
yo ur keybo ard between lines (rather than co ntinuing to type and having the lines wrap aro und), then the wo rds at the
end o f each line and the beginning o f the next line are no t separated by a space; rather, they are separated by a
ne wline character. So when yo u use split (), and yo u split the wo rds up into an array using space (" ") as the split
character, these wo rds (the o nes at the end o f line/beginning o f next line) aren't actually split up. So if "and" appears at
the end o r beginning o f a line, it will no t appear in the array as "and," but rather as "and" co ncatenated with ano ther
wo rd. In my example, I pressed return between "gibe" and "and" so o ne o f the wo rds in the array was
"gibe(newline)and." That wo rd didn't match "and" and that's why I go t 8 as the result rather than 9 .
Typically, split () wo rks best when yo u kno w fo r sure that a given character is used to separate text; fo r instance, in a
CSV file, the "," character is used to delimit the co lumns in the file. It's also used to split smaller pieces o f text, fo r
example, a "firstname lastname" entry co uld be split into "firstname" and "lastname" using split ().
Regular Expressions
So far, we've been searching fo r exact matches fo r the search terms we've tried. Fo r instance, we've tried searching fo r
"and" to see ho w many times the wo rd "and" appears in the text yo u enter to be searched. But what if yo u want to find,
say, all the pho ne numbers in a bit o f text, even if there is mo re than o ne and they are different?
Fo r a task like that yo u need Re gular Expre ssio ns. Regular Expressio ns are a po werful way to express patterns to
match. Yo u'll find Regular Expressio ns used frequently whenever text needs to be matched to a pattern; fo r instance,
yo u can use Regular Expressio ns to verify that the pattern o f text a user enters fo r a pho ne number in a fo rm really
do es lo o k like a pho ne number—o r an email address. There are many uses o f Regular Expressio ns; yo u'll see them
in o ther pro gramming languages, as well as in co mmand line co mmands o r shell scripts.
In JavaScript, there are a few ways to use Regular Expressio ns; we'll talk abo ut o ne o f these: m at ch().
Let's update o ur co de to use a Regular Expressio n and then we'll co me back to talk mo re abo ut ho w Regular
Expressio ns wo rk. They're a bit mysterio us at first, so do n't wo rry if it takes yo u a while to get used to them. Mo dify
st rings.js:
CODE TO TYPE:
window.onload = init;
function init() {
var searchButton = document.getElementById("searchButton");
searchButton.onclick = searchText;
}
function searchText() {
var searchTerm = document.getElementById("searchTerm").value;
var textToSearch = document.getElementById("textToSearch").value;
searchTerm = searchTerm.trim();
textToSearch = textToSearch.trim();
if (searchTerm == null || searchTerm == "") {
alert("Please enter a string to search for");
return;
}
if (textToSearch == null || textToSearch == "") {
alert("Please enter some text to search");
return;
}
}
function clearResultsList(ul) {
while (ul.firstChild) {
ul.removeChild(ul.firstChild);
}
}
function showResults(results) {
var ul = document.getElementById("matchResultsList");
clearResultsList(ul);
var frag = document.createDocumentFragment();
for (var i = 0; i < results.length; i++) {
var li = document.createElement("li");
li.innerHTML = results[i];
frag.appendChild(li);
}
ul.appendChild(frag);
}
Save it. Befo re yo u try it tho ugh, we need to update st rings.ht m l, because we're go ing to update the HTML page
with the results o f o ur search match. We're just creating a list o f matching wo rds in the <ul> element with the id
"matchResultsList," so we need to add this to the page:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Strings</title>
<meta charset="utf-8">
<script src="strings.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
textarea {
width: 700px;
height: 400px;
}
label {
vertical-align: top;
}
</style>
</head>
<body>
<form>
<label for="searchTerm">Search for: </label>
<input type="text" id="searchTerm" size="35"
placeholder="search term">
<input type="button" id="searchButton" value="Search"><br><br>
<label for="textToSearch">Search this text:</label>
<textarea id="textToSearch"></textarea>
</form>
<div>
<h2>Results</h2>
<ul id="matchResultsList">
</ul>
</div>
</body>
</html>
Save the file, and click . Try searching fo r a string. Fo r instance, if yo u enter the text fro m Sherlo ck
Ho lmes again, search fo r "Irene". Yo u'll find two matches; first, yo u'll see an alert that tells yo u ho w many matches yo u
have, and then yo u'll see tho se matches in the results list in the page.
So what? We co uld do that befo re with inde xOf (). Okay, try entering [a-z]+e s\b in the Search fo r field:
No w yo u see a list o f wo rds that end in "es" in yo ur page. That's pretty co o l, right? But yo u're pro bably wo ndering, what
o n earth is [a-z]+e s\b? It's a Regular Expressio n.
But yo u can also pro vide mo re co mplex regular expressio ns, o nes that will match multiple strings. That's
what we did with the regular expressio n "[a-z]+es\b", which matches all wo rds that end in "es".
In o ur co de, we create a Re gular Expre ssio n o bje ct , using the Re gExp() co nstructo r. We pass in the
se archT e rm , which is a pat t e rn—that is, the text yo u typed, like "Irene" o r "[a-z]+es\b"—and a seco nd
argument, "ig." That seco nd argument is a list o f at t ribut e s. There are a few mo difiers yo u can use; "i" and
"g" are co mmo n. "i" says to igno re the case (so we'd match "Irene" to "IRENE" o r "irene" o r "irENe" o r any
co mbo like that), and "g" says to "glo bally" match every instance in the text, rather than just the first o ne:
OBSERVE:
var re = new RegExp(searchTerm, "ig");
This Re gExp o bject can then be used to match strings in so me text. The pattern can be exact, like "Irene", o r it
can be set up to match a variety o f different wo rds, like all wo rds that end in "es", which is what the expressio n
"[a-z]+es\b" do es:
OBSERVE:
To match the pattern to so me text, we use the St ring m e t ho d, m at ch(). m at ch() takes a regular
expressio n re and matches the pattern to the string, in this case t e xt T o Se arch, that we called m at ch() o n.
In the example abo ve, t e xt T o Se arch co ntains the text fro m Sherlo ck Ho lmes. We match that against the
pattern "[a-z]+es\b" with the attributes "ig" (that is, find all instances in the text, regardless o f their case). The
re sult is an array, which we put into a variable, re sult s.
Once we have the re sult s, we can get the le ngt h o f the array to co unt ho w many matches there were, o r we
can display each result in the page like we have:
OBSERVE:
var re = new RegExp(searchTerm, "ig");
var results = textToSearch.match(re);
if (results == null) {
alert("No match found");
}
else {
alert("Found " + results.length + " instances of " + searchTerm);
// Show the matches in the page
showResults(results);
}
We create a functio n sho wRe sult s() that takes the re sult s array and displays the results in the page. We
also create a functio n cle arRe sult sList () to clear the results list each time yo u submit a new search so yo u
get new results each time. All o f the co de in sho wRe sult s() and cle arRe sult sList () is pro bably familiar to
yo u.
OBSERVE:
[0-9]*
[0-9]+
[0-9]{2}
[0 -9 ]* says to match any number o f characters (o r no ne) as lo ng as they are between 0 and 9 . Yo ur results
will sho w all the numbers yo u typed, as well as matches o f no numbers at all fo r the spaces in between the
numbers. Why? Because the * matches zero or more characters. So fo r the first number, say 123, it will match
all tho se digits. Then, fo r the space between 123 and the seco nd number, say, 333, it will match zero digits. It
will include that in the search results, and go o n until it reaches the end o f the numbers yo u input.
[0 -9 ]+ says match o ne o r mo re characters as lo ng as they are between 0 and 9 . Co mpare yo ur results to [0 -
9 ]*. Yo u'll see that yo ur results are limited to the numbers yo u typed o nly; no "zero " results fo r the spaces in
between, right?
[0 -9 ]{2} matches 2 numbers precisely. No tice that it matches the sequences o f two numbers o nly o nce, so if
a number has been matched (like 12 in 123) the same number wo n't be matched again fo r a different
sequence o f two numbers (like 23, where the 2 is fro m the same number, 123).
Matching Characters at a Specific Position
What if yo u want to match o ne o r mo re characters at a specific po sitio n? Try entering so me letters next to a
number, like I did here:
.2 matches any non-white-space character next to the number 2. Try ..2; do yo u see three-character results?
No w yo u're matching two no n-white-space characters next to the number 2.
Yo u can match characters o r sequences o f characters at the end o f a string. Try entering: "I scream, yo u
scream, we all scream fo r ice cream" in the text to search (do n't put a perio d at the end o f the sentence). No w
search fo r:
OBSERVE:
cream$
scream$
cre am $ matches o ne "cream" in that sentence, the o ne at the very end, because $ says match the pattern at
the end o f the string.
scre am $ do esn't match with any results, even tho ugh "scream" appears three times in the sentence,
because "scream" do es no t appear at the end o f the string we're searching.
Yo u can also match o n a wo rd bo undary (rather than the who le string bo undary, like we just did). Using the
same sentence, try it:
OBSERVE:
cream\b
No w yo u see four results. Why? Because the string "cream" appears fo ur times at the end o f a wo rd:
"scream" three times, and "ice cream" o ne time, and in each case, "cream" is at the end o f the wo rd. The \b
characters mean "match a wo rd bo undary" and because we put \b at the end o f "cream", we're saying to
match the end o f the wo rd. Try this instead:
OBSERVE:
rea\b
Yo u see no results, because, while "rea" appears fo ur times in the text, it do esn't appear at the end o f a wo rd.
OBSERVE:
\bscr
Yo u get three matches because yo u're matching "scr" three times in the three instances o f "scream".
[a-z] says match any letter fro m a to z; [a-z]+ says match tho se letters o ne o r mo re times so we can find
wo rds o f any length. Since we're no t matching spaces (o r o ther delimiters, like "," o r ";"), this lo cates o nly
single wo rds. But we are finding o nly wo rds that end in "es" because o f the "es\b". So [a-z]+es\b says: "Find
all wo rds with o ne o r mo re characters matching a-z, ending in es (that is, the characters "es" are o n a wo rd
bo undary at the end o f the wo rd).
Yo u can do much mo re with Regular Expressio n matching; we've hit so me o f the highlights here, but if yo u
like using Regular Expressio ns (and yo u will as yo u write mo re co de!), yo u sho uld check o ut a go o d Regular
Expressio ns reference bo o k o r o nline page fo r the vario us pattern o ptio ns yo u can use. There are many o f
them o ut there.
Regular Expressio ns can be hard to read and understand. Keep that in mind as yo u learn mo re abo ut Regular
Expressio ns. Because they are so hard to read, using them in yo ur pro grams can then make yo ur pro grams
mo re difficult to maintain. Malfo rmed Regular Expressio ns can cause erro rs, so use them judicio usly, and
keep them simple!
Pro pe rt y o r
What It Do e s
Me t ho d
length Pro perty: Returns the number o f characters in the string.
Metho d: Returns the character at a specific po sitio n in the string (remember that strings are 0 -
charAt()
based like arrays).
trim() Metho d: Remo ves leading and trailing spaces (but no t spaces in the middle).
indexOf() Metho d: Returns the po sitio n o f a substring in the o riginal string, o r -1.
to UpperCase() Metho d: Returns a new string that is the upper case versio n o f the o riginal string.
to Lo werCase() Metho d: Returns a new string that is the lo wer case versio n o f the o riginal string.
split() Metho d: Splits a string into substrings based o n a split character, returns an array.
match() Metho d: Matches a Regular Expressio n to a string, returns an array.
These string manipulatio n techniques co me in handy when yo u're pro cessing fo rms o r o ther kinds o f text entered by
the user in a web page.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Dates and Date Formatting
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
Earlier, yo u learned ho w to create a unique key fo r yo ur to -do items in Lo cal Sto rage using the Dat e o bject and the ge t T im e ()
metho d, which returns the current date and time represented as the number o f milliseco nds since 19 70 .
It's time we return to the Dat e o bject, and its metho ds, and explo re the po wer o f this o bject further. The Dat e o bject has many
o f metho ds fo r getting and setting the date and/o r the time; we'll fo cus o n a few o f the mo st useful metho ds.
Dates and times are a bit tricky to wo rk with o n the co mputer. If all yo u care abo ut is the date right no w o n yo ur o wn co mputer,
it's fairly straightfo rward, but if yo u're creating a web page o n the internet, then yo u'll have users fro m all aro und the wo rld in
different time zo nes visiting yo ur site. Wo rking with dates and times in co de can be tricky when yo u're co nsidering all time
zo nes o r, say, co nverting the way we write dates in the US to the way peo ple write dates in o ther co untries. Unfo rtunately,
there's no o ne best way to tackle this stuff, so we'll lo o k at the metho ds we have available, and yo u can experiment with yo ur
o wn co de to see what wo rks best fo r yo u.
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Dates</title>
<meta charset="utf-8">
<script src="dates.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
span {
font-weight: bold;
}
</style>
</head>
<body>
<div>
Right now, the date and time is:
<span id="datetime"></span>
</div>
</body>
</html>
Save it in yo ur /javascript 2 fo lder as dat e s.ht m l. Next, we'll create a new JavaScript file:
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var now = new Date();
datetime.innerHTML = now;
}
Save it in yo ur /javascript 2 fo lder as dat e s.js. Open dat e s.ht m l again and click . Yo u see a page
with the current date and time. Because the JavaScript is running in the bro wser that's o n yo ur co mputer, yo u'll see the
date and time fo r where yo u are, even tho ugh the file is sto red and served fro m the web server at O'Reilly Scho o l o f
Techno lo gy.
In this co de, we create a new Dat e o bject, and then set the co ntent o f the "datetime" <span> element in the HTML to
that date. By default, the value that yo u get is a string that sho ws yo u bo th the date and time.
OBSERVE:
datetime.innerHTML = now;
Here, we set the value o f a pro perty, inne rHT ML, that expects a String, no t a Date. So JavaScript auto matically calls
the Date metho d t o St ring() o n the no w Dat e o bje ct to co nvert it to a String. Try changing the co de to :
OBSERVE:
datetime.innerHTML = now.toString();
t o St ring(): Displays the date and time as a string using the lo cal time zo ne.
t o Dat e St ring(): Displays the date as a string using the lo cal time zo ne.
t o Lo cale Dat e St ring(): Displays the date as a string using the lo cal time zo ne, fo rmatted using
lo cal co nventio ns.
t o T im e St ring(): Displays the time as a string using the lo cal time zo ne.
t o Lo cale T im e St ring(): Displays the time as a string using the lo cal time zo ne, fo rmatted using
lo cal co nventio ns.
t o Lo cale St ring(): Displays the date and time as a string using the lo cal time zo ne, fo rmatted
using lo cal co nventio ns.
No tice the differences in these vario us metho ds o f creating a string fro m the Dat e o bject. Fo r instance,
co mpare the way the date string is displayed using t o Lo cale St ring() with ho w it was displayed using
t o St ring() (abo ve):
Yo u can use the Dat e metho ds, ge t T im e zo ne Of f se t () and t o UT CSt ring() to help figure o ut differences in the
lo cal time where yo u are (o r where so meo ne using yo ur web page is), and the UTC time. Let's update dat e s.js to use
these metho ds to sho w ho w many ho urs we are fro m UTC time:
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var now = new Date();
datetime.innerHTML = now.toUTCString();
var hoursDiff = (now.getTimezoneOffset()) / 60;
datetime.innerHTML += ", " + hoursDiff + " hours difference from me.";
}
Save it, o pen dat e s.ht m l again, and click . No w yo u see the current time expressed in universal time
(o r GMT), and the number o f ho urs difference fro m yo ur lo cal time to GMT.
The ge t T im e zo ne Of f se t () metho d returns the difference in minutes, no t ho urs, so here, we divide by 6 0 to get the
ho urs. Altho ugh yo u may want the time in minutes, because in so me places the time difference fro m GMT do es no t
o ccur o n the ho ur. Fo r instance, so me places will be several ho urs plus a half ho ur different fro m GMT. I'm exactly 7
ho urs behind GMT, so my result is:
Setting a Date and T ime
So far, all we've do ne with Dat e is get the current date, and co nvert it to a String fo r display in a web page. But what if
yo u want to create a specific date?
The Dat e () co nstructo r functio n creates a date that represents now, if yo u do n't pass in any arguments. But yo u can
also create specific dates, by passing in the year, mo nth, day, ho urs, minutes, seco nds, and even milliseco nds o f a
specific date and time yo u want. Let's create the Date that represents New Year's Day, 20 50 . Mo dify dat e s.ht m l as
sho wn:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Dates</title>
<meta charset="utf-8">
<script src="dates.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
span {
font-weight: bold;
}
</style>
</head>
<body>
<div>
Right now, tThe date and time is:
<span id="datetime"></span>
</div>
</body>
</html>
function init() {
var datetime = document.getElementById("datetime");
var now = new Date();
datetime.innerHTML = now.toUTCString();
var hoursDiff = (now.getTimezoneOffset()) / 60;
datetime.innerHTML += ", " + hoursDiff + " hours difference from me.";
var nyd = new Date(2050, 0, 1);
datetime.innerHTML = nyd.toString();
}
OBSERVE:
var nyd = new Date(2050, 0, 1);
In this example, we created a Dat e o bject fo r New Year's Day, 20 50 , by passing in the ye ar 20 5 0 , the m o nt h 0 (fo r
January), and the day 1 (fo r the 1st o f January). No tice that the m o nt h is 0 , no t 1. Why? Because in JavaScript, the
mo nths start at 0 and go to 11. That's a little weird, but that's the way it wo rks. Days, ho wever, do start at 1, and go up to
31 depending o n the mo nth.
Because we didn't supply any arguments fo r the time, JavaScript assumed we wanted 12:0 0 AM (midnight) o n January
1, 20 50 (which is perfect fo r celebrating the New Year!). We co uld have supplied time arguments, like this:
OBSERVE:
var nyd = new Date(2050, 0, 1, 0, 1, 0);
...where 0 is the ho ur (midnight), 1 is the minute (1 minute after midnight), and 0 is the seco nds. Try o ther dates and
times. No tice that if yo u want to specify a time, yo u must also specify a date. That is, while all the arguments to Dat e ()
are o ptio nal, yo u must supply them in o rder, and yo u can't skip any. So , if yo u want to supply minutes, yo u must also
supply a year, mo nth, day, and ho ur, but yo u can skip the seco nds and milliseco nds.
Try changing the co de so yo u display the date and time using t o Lo cale St ring() instead. Mo dify dat e s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var nyd = new Date(2050, 0, 1, 0, 1, 0);
datetime.innerHTML = nyd.toLocaleString();
}
Save it, o pen dat e s.ht m l again, and click . Co mpare yo ur result with the previo us result using
t o St ring(). Which do yo u like better?
In that case, yo u can use the Dat e o bject's se t metho ds. Mo dify dat e s.js as sho wn
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var nyd = new Date(2050, 0, 1, 0, 1, 0);
datetime.innerHTML = nyd.toLocaleString();
var aDate = new Date();
aDate.setFullYear(2020);
aDate.setHours(13);
aDate.setMinutes(3);
aDate.setSeconds(59);
datetime.innerHTML = aDate.toLocaleString();
}
Save it, o pen dat e s.ht m l again, and click . Yo u see yo ur current mo nth and day (that is, the
day yo u are do ing this lesso n), but in the year 20 20 . And the time sho uld be set to 1:0 3pm in yo ur time zo ne.
Here, we created a new Dat e o bject that, by default, represents the present time, and then changed the year to
20 20 , the ho ur to 1pm, and the minute to 3 minutes after 1pm. Everything else stays the same (that is, the
values in place when yo u created the Date representing the present).
Experiment with these o ther metho ds and try using them in yo ur co de to set specific dates and times.
First, let's try co mparing dates. Mo dify dat e s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var aDate = new Date();
aDate.setFullYear(2020);
aDate.setHours(13);
aDate.setMinutes(3);
aDate.setSeconds(59);
datetime.innerHTML = aDate.toLocaleString();
var now = new Date();
var diff = aDate.getTime() - now.getTime();
var days = diff / 1000 / 60 / 60 / 24;
datetime.innerHTML = aDate.toLocaleString() + ", " + days + " days from now"
;
}
Save it, o pen dat e s.ht m l again, and click . Yo u'll see the number o f days between no w and
the same date in 20 20 (and remember, yo ur date will be different fro m mine because the dates are based o n
now, that is, the date and time yo u are do ing this lesso n).
To co mpute the difference between two dates, we create two Dat e o bjects. We have o ne Dat e o bject fo r the
date in 20 20 , and o ne Dat e o bject fo r no w. Then, we can use the ge t T im e () metho d to get the date and time
in milliseco nds since 19 70 . The number o f milliseco nds to the date in 20 20 will be lo nger than the number o f
milliseco nds to no w (assuming it's still befo re 20 20 o f co urse!). So we subtract the milliseco nds to no w fro m
the milliseco nds to the date in 20 20 to get the difference in milliseco nds. Then, we co nvert fro m milliseco nds
to days by dividing by 10 0 0 (the number o f milliseco nds in a seco nd), then 6 0 (the number o f seco nds in a
minute), then 6 0 again (the number o f minutes in an ho ur), and then 24 (the number o f ho urs in a day). The
result is the number o f days between no w and the date in 20 20 .
That number is kind o f ugly when we display it because o f the precisio n o f the number after the decimal po int.
Let's cut o ff everything after the decimal po int to make it easier to read. We can use the Mat h.f lo o r() metho d
to do this. Mo dify dat e s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var aDate = new Date();
aDate.setFullYear(2020);
aDate.setHours(13);
aDate.setMinutes(3);
var now = new Date();
var diff = aDate.getTime() - now.getTime();
var days = Math.floor(diff / 1000 / 60 / 60 / 24);
datetime.innerHTML = aDate.toLocaleString() + ", " + days + " days from now"
;
}
Save it, o pen dat e s.ht m l, and click . No w the number o f days is be easier to read:
Remember that Mat h is a built-in JavaScript o bject with lo ts o f handy metho ds yo u can use fo r do ing math
co mputatio ns. Mat h.f lo o r() dro ps all o f the numbers after the decimal po int in a flo ating po int number, so
yo u get a number that is less than (o r equal to , if the number is even) the o riginal. Co mpare that to
Mat h.ce il() which ro unds up and then dro ps the numbers.
No w let's try creating a date that's three days fro m no w. Mo dify dat e s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var aDate = new Date();
aDate.setFullYear(2020);
aDate.setHours(13);
aDate.setMinutes(3);
var now = new Date();
var diff = aDate.getTime() - now.getTime();
var days = Math.floor(diff / 1000 / 60 / 60 / 24);
datetime.innerHTML = aDate.toLocaleString() + ", " + days + " days from now"
;
var now = new Date();
var threeDays = (24 * 60 * 60 * 1000) * 3;
var threeDaysFromNow = new Date(now.getTime() + threeDays);
datetime.innerHTML = now.toLocaleString() + "; 3 days from now: " + threeDay
sFromNow.toLocaleString();
}
Save it, o pen dat e s.ht m l, and click . Yo u see two dates: no w, and three days fro m no w.
We also use the date expressed as milliseco nds since 19 70 to create a date three days fro m no w. Here, we
create a Dat e representing no w. Then we figure o ut ho w many milliseco nds are in three days, and create
ano ther Dat e that is no w (expressed in milliseco nds) plus the number o f milliseco nds in three days. That
gives us a Dat e three days fro m no w.
Experiment by creating o ther dates. Can yo u create a date that is three days in the past fro m no w?
When yo u submit a date using a fo rm, whether yo u're using <input type="date"> o r <input type="text">, the value yo u
get when yo u pro cess the input data with JavaScript is a String. But so metimes yo u might need that value as a Dat e
o bject; that's when yo u'll use the techniques we're learning in this lesso n.
The Dat e o bject has a metho d named parse () that yo u can use to parse a string representing a date. Let's see ho w
we can use it in an example. First, we'll update o ur HTML to add a fo rm entry fo r a date, and then update o ur JavaScript
to pro cess the string value we get fro m the fo rm. We'll co nvert the string into a Date, and then display the Date in the
page, in the "datetime" <span>. Mo dify dat e s.ht m l as sho wn:
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Dates</title>
<meta charset="utf-8">
<script src="dates.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
span {
font-weight: bold;
}
form {
margin-bottom: 20px;
}
</style>
</head>
<body>
<form>
<label>Enter a date:</label>
<input type="date" id="aDate">
<input type="button" id="submit" value="Submit">
</form>
<div>
The date and time is:
<span id="datetime"></span>
</div>
</body>
</html>
CODE TO TYPE:
window.onload = init;
function init() {
var datetime = document.getElementById("datetime");
var now = new Date();
var threeDays = (24 * 60 * 60 * 1000) * 3;
var threeDaysFromNow = new Date(now.getTime() + threeDays);
datetime.innerHTML = now.toLocaleString() + ", 3 days from now: " + threeDaysFromNo
w.toLocaleString();
var submit = document.getElementById("submit");
submit.onclick = getDate;
}
function getDate() {
var aDateString = document.getElementById("aDate").value;
if (aDateString == null || aDateString == "") {
alert("Please enter a date");
return;
}
var aDateMillis = Date.parse(aDateString);
alert(aDateMillis);
var aDate = new Date(aDateMillis);
Save it, o pen dat e s.ht m l again, and click . Enter a date in the date input field. Click Subm it . Yo u first
see an alert, and then yo u see a date displayed in the page belo w the fo rm input. Try writing the date in different
fo rmats.
Yo u pro bably see the string "NaN" in the alert, and yo u pro bably get a date that makes no sense in the page. If yo u're
using a bro wser like Safari o r Chro me that displays arro ws next to the "date" input co ntro l, and yo u use these to enter
a date, like "0 7-19 -20 12," yo u'll still see a no nsensical date displayed in the page.
So mething's definitely go ne wro ng, because that date do esn't make sense.
Yo u'll likely find that this co de wo rks pro perly o nly when yo u enter dates in particular fo rmats. One o f the fo rmats yo u
can use is the same fo rmat yo u see when yo u use t o St ring() o r t o Lo cale St ring(), as we've do ne in these
examples. Try entering a date using this fo rmat:
No w it sho uld wo rk. Yo u see an alert with the number o f milliseco nds representing that date, and the date appears
pro perly under the fo rm.
Here are so me o ther fo rmats yo u can try:
July 20 , 20 12
20 12/7/20
20 12.7.20
20 12-7-20
7/20 /20 12
7-20 -20 12
7.20 .20 12
Take no te o f the different fo rmats yo u try, which o nes wo rk, and which o nes do n't.
OBSERVE:
function getDate() {
var aDateString = document.getElementById("aDate").value;
if (aDateString == null || aDateString == "") {
alert("Please enter a date");
return;
}
var aDateMillis = Date.parse(aDateString);
alert(aDateMillis);
var aDate = new Date(aDateMillis);
First, we ge t t he value o f t he " aDat e " input as a string. We che ck t o m ake sure t he st ring isn't e m pt y; if it is,
we ale rt t he use r and ask them to enter a date, and then return fro m the functio n.
If we get a string, we t ry t o parse it using Dat e .parse (). Dat e .parse () will return the date in milliseco nds fro m the
string yo u pass in, but o nly if the metho d can parse the string. As yo u've disco vered, there are o nly certain string
fo rmats that Dat e .parse () will parse co rrectly. If Dat e .parse () fails, then instead o f returning the milliseco nds (a large
number), it returns "NaN," meaning "No t a Number." That's JavaScript's way o f letting yo u kno w it co uldn't parse this
string into a number. So , if yo u enter a date using the wro ng type o f string fo rmat, yo u see "NaN" in the ale rt .
After getting the date (o r trying to get the date) in milliseco nds using Dat e .parse (), we create a new Dat e o bject fro m
that value. If we're successful, aDat e will be a valid Dat e o bject. If no t, aDat e will be a no nsensical date, because the
Dat e () co nstructo r can't make sense o f the value, "NaN".
Finally, we display the Dat e in the " dat e t im e " <span> using the t o Lo cale St ring() metho d.
We've been using Dat e o bjects by using the Dat e () co nstructo r to create a date o bject and then calling
metho ds o n that o bject. So what is Dat e .parse ()? Dat e .parse () is an example o f a st at ic m e t ho d. In
JavaScript, functio ns are o bjects; so the Dat e () co nstructo r functio n that yo u use to create new date
o bjects is, itself, an o bject. And it so happens that that o bject has a metho d named parse (). Yo u call
Note static metho ds using the name o f the co nstructo r functio n, Dat e , but witho ut the parentheses () that
invo ke the functio n. Do n't wo rry if yo u do n't fully understand this; this to pic is mo re advanced JavaScript
and isn't within the sco pe o f this co urse. Still, yo u're getting a taste o f the kinds o f things yo u can do with
JavaScript when yo u delve into object-oriented programming. But, that's a to pic fo r ano ther co urse...
We already checked to make sure the user is entering so mething into the field (by checking to see if the input
is null o r the empty string); we sho uld also be checking to make sure that the user is entering a valid date.
We'll tackle this using Exce pt io n Handling in the next lesso n.
In this lesso n, we've explo red the JavaScript Dat e o bject. Yo u've learned ho w to create Dates, display Dates, co mpare Dates,
and co nvert strings to Dates. Yo u've also learned a few ins and o uts o f time zo nes. As yo u can see, wo rking with Dates can
so metimes be tricky! But fun to o , right? Take a sho rt break and then yo u'll be ready to tackle mo re Dates in the pro ject befo re
yo u mo ve o n to the next lesso n.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Handling Exceptions with Try/Catch
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
As yo u have realized by no w, there are plenty o f things that can go wro ng when writing JavaScript co de! There are unexpected
events, bugs, and mistyped data submitted by users. So far we've handled these exceptional conditions and errors either by
igno ring them (no t a go o d lo ng-term plan!) o r by testing with if/then/else statements to check fo r certain co nditio ns.
JavaScript includes ano ther way o f handling exceptions: the t ry/cat ch statement. T ry/cat ch lets yo u try so me co de and if
so mething go es wro ng, yo u can catch the erro r and do so mething abo ut it.
In this lesso n yo u'll learn ho w to use t ry/cat ch (and the o ptio nal f inally part o f this statement) to handle exceptio ns.
So what kind o f co de causes an exceptio n that causes yo ur co de to sto p running? Let's take a lo o k at an example:
INTERACTIVE SESSION:
Open a bro wser windo w, and o pen the JavaScript co nso le (yo u may have to lo ad a web page to be able to access the
co nso le; any web page will do , including a previo us file yo u've created in the co urse). Type in tho se two lines; yo u see
a JavaScript Erro r like this (in Safari):
Erro r messages in vario us bro wsers will differ slightly, but all bro wsers sho uld give yo u an exceptio n fo r
Note this co de, and a similar erro r message. Try different bro wsers to see what yo u get!
An erro r like this in yo ur co de will cause the co de to sto p executing. Let's try making an erro r in co de lo aded with an
HTML page (rather than just at the co nso le), and this time, we'll t ry it and cat ch the erro r with the t ry/cat ch
statements. First, we'll create a super-simple HTML page, and then the JavaScript to create the erro r.
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Exceptions</title>
<meta charset="utf-8">
<script src="ex.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
</body>
</html>
Save it in yo ur /javascript 2 fo lder as e x.ht m l. Next, create a new JavaScript file as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var myString = null;
try {
var len = myString.length;
console.log("Len: " + len);
}
catch (ex) {
console.log("Error: " + ex.message);
}
}
Save it in yo ur /javascript 2 fo lder as e x.js. Open e x.ht m l again, and click . Open the JavaScript
co nso le and yo u see an erro r message like this (in Safari):
OBSERVE:
TypeError: 'null' is not an object (evaluating 'myString.length')
OBSERVE:
TypeError: myString is null
The erro r messages generated fro m yo ur JavaScript co de are the same as tho se yo u saw using the co nso le earlier.
After setting m ySt ring to null, which we kno w will cause an erro r when we try to access the le ngt h pro perty (because
null do esn't have a length pro perty), we start the t ry/cat ch blo ck. We use { and } to delimit each part o f the statement;
these are required.
JavaScript will try to execute all the co de in the t ry part o f the statement. If it wo rks and no exceptio n is created, then the
t ry ends no rmally, and the cat ch is not executed. So the flo w o f executio n wo uld co ntinue belo w the cat ch; in this
case, that means the functio n simply returns.
But if so mething go es wro ng, and an exceptio n is generated, as we kno w it will in this co de, then as so o n as the
exceptio n is generated, the flo w o f executio n jumps fro m the t ry to the cat ch. In this case, that means we never see
the co nso le .lo g() message that displays the value o f le n.
JavaScript auto matically generates a value f o r t he e xce pt io n and passes it into the cat ch clause (much like
passing an argument to a functio n parameter). Fo r erro rs generated internally by the JavaScript interpreter, like this o ne
is, that value is typically an Erro r o bject. Here, we assume it is such an erro r, and we name that o bject e x, and access
its m e ssage pro perty to display in the co nso le, with info rmatio n abo ut what the erro r was.
So in this co de, we cause, o r raise (as it's o ften called), an exceptio n by attempting to access a pro perty that do esn't
exist, and we catch that exceptio n so that it do esn't cause o ur pro gram to sto p running entirely. This is usually better fo r
the applicatio n; if yo u handle the erro rs that are caused in yo ur pro gram gracefully, then the end user can co ntinue
using yo ur applicatio n, whereas if yo ur JavaScript sto ps running, that might cause yo ur applicatio n to sto p wo rking
alto gether!
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>Exceptions</title>
<meta charset="utf-8">
<script src="ex.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Enter a string</h1>
<p id="stringInfo"></p>
<p id="error"></p>
<p id="msg"></p>
</body>
</html>
Save it. No w, update e x.js:
CODE TO TYPE:
window.onload = init;
function init() {
var myString = null;
try {
var len = myString.length;
console.log("Len: " + len);
}
catch (ex) {
console.log("Error: " + ex.message);
}
var myString = prompt("Enter a string:");
try {
var len = myString.length;
if (len == 0) {
throw new Error("You didn't enter anything. Try again.");
}
else {
displayLength(myString, len);
}
}
catch (ex) {
displayError(ex.message);
}
finally {
displayMessage("Thanks for trying!");
}
}
function displayError(e) {
var error = document.getElementById("error");
error.innerHTML = e;
}
function displayMessage(m) {
var msg = document.getElementById("msg");
msg.innerHTML = m;
}
Save it, o pen e x.ht m l again, and click . Yo u'll be pro mpted to enter a string. Try entering a real
string; see what happens. No w try clicking OK witho ut entering anything, an see what happens.
Let's go thro ugh the co de to see what's go ing o n:
OBSERVE:
function init() {
var myString = prompt("Enter a string:");
try {
var len = myString.length;
if (len == 0) {
throw new Error("You didn't enter anything. Try again.");
}
else {
displayLength(myString, len);
}
}
catch (ex) {
displayError(ex.message);
}
finally {
displayMessage("Thanks for trying!");
}
}
No w, instead o f setting m ySt ring to null, we're pro mpting the user to enter a string. Once we've do ne that, we
t ry to get the length o f the string. When yo u use pro m pt (), even if yo u do n't enter anything, the value returned
is still a string (in that case, it wo uld be an empty string, ""), and getting the length o f an empty string will result
in 0 , rather than an exceptio n. We can che ck t o se e if t he le ngt h is 0 , and if it is, we can t hro w o ur o wn
e xce pt io n. Yo u can thro w any value yo u want; in this case, we are thro wing an Erro r o bject (just like
JavaScript did in the previo us example). As so o n as we t hro w t he Erro r, the co de skips any o ther co de in
the t ry clause, and jumps to the cat ch clause. There, we pass the value o f the m e ssage pro perty fro m the
Erro r o bject to the functio n displayErro r(), which displays that value in the web page. The value o f
m e ssage is the value we passed to the Erro r() co nstructo r when we t hre w t he Erro r.
If t he le ngt h is gre at e r t han 0 , we display the string and the length o f the string by passing the two values
to displayLe ngt h().
T he Finally Clause
We added o n a f inally clause in this example. This clause is executed whether the exceptio n is thro wn o r no t.
In o ther wo rds, if the length is 0 , and we execute the cat ch clause, o nce the cat ch is co mplete, we execute
the f inally clause. If the length is greater than 0 , we execute the co de in the t ry clause, skip the cat ch clause
(because there's no exceptio n), and execute the f inally clause.
Finally allo ws yo u execute so me co de regardless o f what happens in the t ry/cat ch, so it's handy fo r clean-
up co de, fo r example. In this case, all we do is display the same message whether the pro mpt is successful
o r no t.
This is a go o d example o f where we can use t ry/cat ch and thro w an e xce pt io n instead. Do ing this will make the
co de mo re ro bust; it's a go o d way to handle this type o f erro r. Let's give it a try. Edit dat e s.js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var submit = document.getElementById("submit");
submit.onclick = getDate;
}
function getDate() {
var aDate;
var aDateString = document.getElementById("aDate").value;
if (aDateString == null || aDateString == "") {
alert("Please enter a date");
return;
}
var aDateMillis = Date.parse(aDateString);
alert(aDateMillis);
var aDate = new Date(aDateMillis);
try {
if (isNaN(aDateMillis)) {
throw new Error("Date format error. Please enter the date in the format MM/
DD/YYYY, YYYY/MM/DD, or January 1, 2012");
}
else {
aDate = new Date(aDateMillis);
}
var datetime = document.getElementById("datetime");
datetime.innerHTML = aDate.toLocaleString();
}
catch (ex) {
alert(ex.message);
}
}
Save it, o pen dat e s.ht m l, and click . Try entering a string using a fo rmat the pro gram will reco gnize,
and a string that it will no t reco gnize. Yo u see the same behavio r yo u saw at the end o f the previo us lesso n, but the
way we handle the erro r (that is, the fo rmat that the pro gram can't parse) is different. No w we use t ry/cat ch and thro w
an exceptio n if we can't match the date fo rmat the user has entered.
No tice that as so o n as we thro w the Erro r o bject when we can't match the fo rmat, the rest o f the t ry clause is skipped,
so we do n't have to check to see if an erro r was generated. As so o n as that Erro r is thro wn, the co de jumps directly to
the cat ch clause. This is a co nvenient way to tell yo ur pro gram to , "sto p everything we're do ing and go here!" This
technique can be really useful. It can also make the co de a bit easier to read.
Remember that the t ry clause must always be matched with either a cat ch o r a f inally. Typically, yo u'll see t ry/cat ch, but
yo u'll find f inally will also co me in handy. If yo u want to raise yo ur o wn exceptio ns, yo u can use t hro w and thro w a value that
is caught by the cat ch clause parameter. Yo u can thro w any value yo u want; JavaScript typically thro ws the Erro r o bject, and
yo u can do this to o by creating a ne w Erro r o bject, and passing in the value fo r the m e ssage pro perty. Always check the
JavaScript do cumentatio n in a go o d reference to find o ut exactly which type o f exceptio n to expect fo r circumstances where
JavaScript might no t thro w the Erro r o bject, so yo u kno w which kind o f value to expect in yo ur cat ch clause.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Geolocation and Google Maps
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
use the geo lo catio n o bject to get yo ur po sitio n co o rdinates (latitude and lo ngitude).
put yo ur lo catio n into a webpage using geo lo catio n.
handle geo lo catio n erro rs.
add a Go o gle map.
add a marker to yo ur map.
One fairly new feature in JavaScript and web bro wsers is Geo lo catio n. It's been aro und in vario us fo rms fo r a while, but it was
standardized recently thro ugh the W3C in the Geo lo catio n specificatio n. All mo dern bro wsers (including IE9 +) no w suppo rt this
versio n o f Geo lo catio n, which makes it easier to get lo catio n data into yo ur web pages.
Geo lo catio n is especially fun when yo u're using a web applicatio n o n a mo bile bro wser that suppo rts Geo lo catio n, because
yo u're likely to be mo ving aro und, and mo re likely to be using an app where seeing yo ur lo catio n co mes in handy. Fo rtunately,
Geo lo catio n is suppo rted by bo th the iOS bro wser (o n iPho ne and iPad) and the Andro id bro wser (o n a variety o f smart
pho nes).
In this lesso n we'll build a simple "Rando m Tho ughts" applicatio n that allo ws yo u to add rando m tho ughts to a web page. The
app will capture yo ur lo catio n when yo u begin adding yo ur tho ughts and add it to a map. So und like fun? Let's get go ing!
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>My Random Thoughts</title>
<meta charset="utf-8">
<script src="random.js"></script>
<style>
</style>
</head>
<body>
</body>
</html>
Save it in yo ur /javascript 2 fo lder as rando m .ht m l. Next, create the JavaScript in a new file:
CODE TO TYPE:
window.onload = init;
function init() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getLocation);
}
else {
console.log("Sorry, no Geolocation support!");
}
}
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
alert("My position is: " + latitude + ", " + longitude);
}
Save it in yo ur /javascript 2 fo lder as rando m .js, o pen rando m .ht m l again, and click . Yo u're
pro mpted to co nfirm that yo u're o kay with sharing yo ur lo catio n. This is to pro tect yo ur privacy. Here's what the pro mpt
lo o ks like in Safari:
In Chro me:
And in Firefo x:
Once yo u allo w the bro wser to use yo ur lo catio n (assuming yo u're o kay with that, and we'll talk mo re later abo ut what
happens if yo u do n't allo w it), then yo u'll see an alert with yo ur lo catio n:
If yo u do n't see an alert, we'll add so me co de so o n that yo u can use to help tro ublesho o t whatever the pro blem might
be. Assuming yo u're using a mo dern bro wser, yo u'll see a lo catio n, even if yo u're o n a deskto p machine. Ho wever,
so metimes yo ur lo catio n might be based o n yo ur ISP's netwo rk hub rather than yo ur actual lo catio n, so it may no t be
as precise o n a deskto p co mputer as it wo uld be, say, o n a pho ne with GPS. We'll review the vario us ways bro wsers
determine yo ur lo catio n sho rtly.
OBSERVE:
function init() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getLocation);
}
else {
console.log("Sorry, no Geolocation support!");
}
}
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
alert("My position is: " + latitude + ", " + longitude);
}
First, in the init () functio n that runs when the page has lo aded, we che ck t o se e if t he navigat o r.ge o lo cat io n
o bject exists. We kno w navigat o r exists (all bro wsers have this o bject), but so me bro wsers might no t have the
ge o lo cat io n o bject.
If the ge o lo cat io n o bject exists, then we call its ge t Curre nt Po sit io n() metho d. The argum e nt we pass to
ge t Curre nt Po sit io n() is a functio n value, ge t Lo cat io n. If this is the first time yo u've seen a functio n passed as an
argument, yo u might be wo ndering, ho w o n earth do es that wo rk?
Well, remember that JavaScript functio ns are values that can be saved in pro perties (like we do when we say
windo w.o nlo ad = init ) o r sto red in variables (like we do when we set an o bject's pro perty name to a functio n value),
o r passed as arguments to o ther functio ns. In this case, we're passing the name o f a callback function, which we've
named ge t Lo cat io n, to ge t Curre nt Po sit io n, so that Geo lo catio n can call that functio n when the bro wser has
successfully retrieved yo ur lo catio n. Here's ho w it wo rks:
Just like o ur windo w.o nlo ad handler functio n, init () is called when the page is lo aded, the ge t Lo cat io n() functio n
is called when the bro wser has retrieved yo ur lo catio n. Remember to pass o nly the name o f the functio n; do not write:
OBSERVE:
navigator.geolocation.getCurrentPosition(getLocation());
Why? Because that wo uld call the functio n and try to pass the value the functio n returns to ge t Curre nt Po sit io n(),
which is no t what we want! We want to pass the functio n value to ge t Curre nt Po sit io n(), so ge t Curre nt Po sit io n()
calls the callback fo r us.
Once the bro wser has retrieved yo ur lo catio n successfully, it tells Geo lo catio n to call yo ur callback functio n,
ge t Lo cat io n(), and then passes yo ur lo catio n to this functio n as a po sit io n o bject.
The po sit io n o bject co ntains ano ther o bject, co o rds, which has two pro perties we're interested in: lat it ude and
lo ngit ude . These two pro perties are the co o rdinates o f yo ur lo catio n. Fo r no w, all we're do ing is saving tho se values
in variables, and using ale rt () to display them.
Latitude and lo ngitude are o ften specified in degrees, minutes, and seco nds (which yo u may be familiar with if
yo u're an astro no mer), o r as decimal values. In the Geo lo catio n API, we always deal with the decimal values,
so the values yo u get fro m the po sit io n o bject are the decimal versio ns o f latitude and lo ngitude.
GPS: this is available o n many smart pho nes and o ther GPS-enabled devices, and is the mo st
accurate way to get yo ur lo catio n. These devices use data fro m satellites to get yo ur lo catio n. In
these devices, the bro wser has access to the GPS info rmatio n (assuming yo u have GPS turned
o n, which is a real battery drainer, so do n't fo rget to turn it o ff when yo u do n't need it!).
Cell Pho ne To wer Triangulatio n: If yo u're o n a cell pho ne witho ut GPS (o r yo u have it turned o ff),
then tho se cell pho nes can still get a ro ugh idea o f yo ur lo catio n by seeing which cell pho ne to wers
can see yo ur pho ne. The mo re to wers yo u're near, the mo re accurate yo ur lo catio n will be.
WiFi: Like cell pho ne to wer triangulatio n, WiFi po sitio ning uses o ne o r mo re WiFi access po ints to
co mpute yo ur lo catio n. This is handy when yo u're indo o rs o n yo ur lapto p.
IP Address: If yo u're co nnected to a wired netwo rk, then this is the metho d yo u'll use (like, o n yo ur
deskto p co mputer). In this case, yo ur IP address is mapped to a lo catio n via a lo catio n database.
This has the advantage o f being able to wo rk anywhere, but it's o ften less accurate, because
so metimes the database maps yo ur IP address to yo ur ISP's lo catio n, o r yo ur neighbo rho o d,
rather than yo ur specific lo catio n.
Whatever metho d yo ur device o r co mputer uses to get yo ur lo catio n, o nce that lo catio n is fo und, the bro wser
can then get that back to yo ur JavaScript co de using the po sit io n o bject, and yo ur callback functio n.
Save it. We added a fo rm to enter a tho ught, added a list, "tho ughts," that we'll use to display the tho ughts in the
page, and a "map" <div>, where we'll display the lo catio n o f the tho ughts. Do n't preview yet; first, mo dify rando m .js as
sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getMyLocation);
}
else {
console.log("Sorry, no Geolocation support!");
}
}
function getMyLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
alert("My position is: " + latitude + ", " + longitude);
}
function init() {
var submit = document.getElementById("submit");
submit.onclick = getThought;
}
function getThought() {
var aThought = document.getElementById("aThought").value;
if (aThought == null || aThought == "") {
alert("Please enter a thought with at least one word");
return;
}
var id = (new Date()).getTime();
var thought = new Thought(id, aThought);
addThoughtToPage(thought);
}
function addThoughtToPage(thought) {
var ul = document.getElementById("thoughts");
var li = document.createElement("li");
li.setAttribute("id", thought.id);
li.appendChild(spanText);
ul.appendChild(li);
}
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
var mapDiv = document.getElementById("map");
mapDiv.innerHTML = "I'm thinking at " + latitude + ", " + longitude;
}
Save it, o pen rando m .ht m l again, and click . Enter a tho ught in the fo rm input text co ntro l, and click
Subm it . Yo u'll be pro mpted to co nfirm that yo u're o kay with sharing yo ur lo catio n. Appro ve the request to share, and
see what happens. Here's what yo u sho uld see if yo ur Geo lo catio n is wo rking well:
function init() {
var submit = document.getElementById("submit");
submit.onclick = getThought;
}
function getThought() {
var aThought = document.getElementById("aThought").value;
if (aThought == null || aThought == "") {
alert("Please enter a thought with at least one word");
return;
}
var id = (new Date()).getTime();
var thought = new Thought(id, aThought);
addThoughtToPage(thought);
}
function addThoughtToPage(thought) {
var ul = document.getElementById("thoughts");
var li = document.createElement("li");
li.setAttribute("id", thought.id);
li.appendChild(spanText);
ul.appendChild(li);
}
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
var mapDiv = document.getElementById("map");
mapDiv.innerHTML = "I'm thinking at " + latitude + ", " + longitude;
}
First, we added an o bject co nstructo r functio n to create T ho ught o bjects. A T ho ught o bject has an id and so me text
that the user types into the fo rm.
When the use r clicks t he Subm it but t o n in the fo rm, we call the ge t T ho ught () functio n and check to make sure
the user really typed so mething in the fo rm. If they did, we create a new T ho ught o bject, by first creating a unique id
(using the time in milliseco nds like we did in the lesso n o n Dates and Date Fo rmatting), and then using the co nstructo r
to create a new T ho ught o bject, passing in the id and the text o f the tho ught.
Next we want to get the user's lo catio n, so we can add the lo catio n po sitio n info rmatio n to the page. To do that, we use
the ge o lo cat io n o bject again, call the ge t Curre nt Po sit io n() metho d, passing in the ge t Lo cat io n() functio n, like
we did befo re.
Yo u can see that ge t Lo cat io n() has o ne parameter, the po sit io n o bject. ge t Lo cat io n() gets yo ur latitude and
lo ngitude fro m po sit io n, just like befo re, and then updates the page with this data.
Lo o king back up at the ge t T ho ught () functio n, after we call the ge t Curre nt Po sit io n() metho d o f ge o lo cat io n,
we call ano ther functio n, addT ho ught T o Page (), passing the tho ught that needs to be added to the page. The
addT ho ught T o Page () functio n adds the tho ught info rmatio n to the page by creating a new <li> element and adding
the data fro m the t ho ught o bject it's been passed: the id and the text o f the tho ught.
Handling Errors
So , what if so mething go es wro ng when the bro wser tries to get yo ur lo catio n? Or, what happens if yo u do n't allo w
yo ur po sitio n to be shared with the bro wser? If yo u haven't been able to see any lo catio n data, the call to
ge t Curre nt Po sit io n() is likely failing and no lo catio n is being retrieved. If yo u have been getting yo ur lo catio n
successfully, try shift-relo ading the page, and deny the request fro m the bro wser to use yo ur lo catio n. What happens?
It wo uld be nice fo r yo ur applicatio n to kno w a little bit mo re abo ut what went wro ng. We can pass a seco nd callback
functio n, an e rro r callback f unct io n, to ge t Curre nt Po sit io n() that will be called if ge t Curre nt Po sit io n() is
unable to retrieve a lo catio n fro m the bro wser. Let's see ho w that wo rks. Mo dify rando m .js as sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var submit = document.getElementById("submit");
submit.onclick = getThought;
}
function getThought() {
var aThought = document.getElementById("aThought").value;
if (aThought == null || aThought == "") {
alert("Please enter a thought with at least one word");
return;
}
var id = (new Date()).getTime();
var thought = new Thought(id, aThought);
addThoughtToPage(thought);
}
function addThoughtToPage(thought) {
var ul = document.getElementById("thoughts");
var li = document.createElement("li");
li.setAttribute("id", thought.id);
li.appendChild(spanText);
ul.appendChild(li);
}
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
var mapDiv = document.getElementById("map");
mapDiv.innerHTML = "I'm thinking at " + latitude + ", " + longitude;
}
function locationError(error) {
var errorTypes = {
0: "Unknown error",
1: "Permission denied by user",
2: "Position not available",
3: "Request timed out"
};
var errorMessage = errorTypes[error.code];
if (error.code == 0 || error.code == 2) {
errorMessage += " " + error.message;
}
console.log(errorMessage);
alert(errorMessage);
}
Save it, o pen rando m .ht m l again, and click . If yo u were no t seeing lo catio n info rmatio n befo re, yo u
sho uld see an alert no w with mo re info rmatio n abo ut what went wro ng. If yo u were seeing lo catio n info rmatio n befo re,
go ahead and deny the bro wser's request to use yo ur lo catio n, and again, yo u sho uld see an alert with the erro r
message, like this:
So me bro wsers may pro mpt yo u to Always o r Never allo w the lo catio n info rmatio n, so if yo u deny the
request, yo u may need to go to the appro priate setting in the bro wser to set it back to allo w o r pro mpt fo r
permissio n. In so me bro wsers, yo u can just relo ad the page, o r clo se and restart rando m .ht m l. Others
may require that yo u change the o ptio n back in the To o ls area.
To reset the permissio n in Firefo x, select T o o ls | Page Inf o | Pe rm issio ns and change the
Note permissio n fo r Share Lo cat io n.
To reset the permissio n in Chro me, select Chro m e | Pre f e re nce s | Se t t ings, click o n Advance d
Pe rm issio ns, then Privacy | Co nt e nt Se t t ings | Lo cat io n and change the permissio n to Ask m e
whe n a sit e t rie s t o t rack m y physical lo cat io n. Then click o n Manage Expe ct at io ns to see which
sites are listed to Allo w o r Blo ck auto matically, and remo ve yo ur o reillystudent.co m site if it's listed there,
so the bro wser will pro mpt yo u to Allo w o r Deny the next time yo u try.
OBSERVE:
...
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getLocation, locationError);
}
.
.
.
function locationError(error) {
var errorTypes = {
0: "Unknown error",
1: "Permission denied by user",
2: "Position not available",
3: "Request timed out"
};
var errorMessage = errorTypes[error.code];
if (error.code == 0 || error.code == 2) {
errorMessage += " " + error.message;
}
console.log(errorMessage);
alert(errorMessage);
}
Here we added a seco nd argument to the call to ge t Curre nt Po sit io n(), passing in a seco nd callback functio n,
lo cat io nErro r(). The error callback handler is passed info rmatio n fro m the bro wser when it's called: an e rro r o bject.
The e rro r o bject has a pro perty, co de , that co ntains a number representing the type o f erro r. In lo cat io nErro r(), we
map that co de to an erro r message using an e rro rT ype s o bject literal. If the erro r co de is 0 o r 2, so metimes we can
retrieve mo re info rmatio n abo ut what went wro ng in the e rro r o bject's m e ssage pro perty. We then display the full
erro r message bo th to the co nso le and in an alert().
If yo u cause an erro r by denying the bro wser's request to use yo ur lo catio n, yo u generate an erro r co de o f type 1, and
yo u see the message "Permissio n denied by user." If yo u see o ne o f the o ther erro r messages, the bro wser is unable
to access yo ur po sitio n fo r so me o ther reaso n. It co uld be that yo ur signal isn't stro ng eno ugh o n yo ur cell pho ne (o r
yo u're o ut o f range o f a cell to wer), o r yo ur GPS is turned o ff, o r yo ur ISP isn't mapped to a lo catio n in the lo catio n
database, fo r instance.
If yo u see an erro r message no w, what do es it say? If yo u're appro ving the request to share yo ur lo catio n, but still
getting an erro r, try yo ur pro gram o n ano ther co mputer o r device if yo u have o ne available. Ideally, yo ur pro gram
wo rks successfully at this po int, and yo u wo n't need the erro r handling co de, but it's still go o d to have it in there.
No te that even if lo cat io nErro r() is called, we still add the tho ught to the page, using addT ho ught T o Page (), so the
basic app still wo rks; it just do esn't sho w yo u a lo catio n fo r the tho ught.
CODE TO TYPE:
<!doctype html>
<html>
<head>
<title>My Random Thoughts</title>
<meta charset="utf-8">
<script src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script src="random.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
form {
margin-bottom: 20px;
}
div#map {
width: 400px;
height: 400px;
}
</style>
</head>
<body>
<form>
<label>Enter a random thought:</label>
<input type="text" id="aThought">
<input type="button" id="submit" value="Submit">
</form>
Save it. We added the link to the Go o gle Maps API JavaScript in the line:
OBSERVE:
<script src="http://maps.google.com/maps/api/js?sensor=true"></script>
This includes all the JavaScript fo r the Go o gle Maps API, so when yo u use the Go o gle functio ns to create a map, the
bro wser will be able to find the JavaScript. se nso r=t rue o n the end o f the URL is required. It tells the maps API that
we're using the bro wser's Geo lo catio n capabilities to get o ur lo catio n.
Next, we'll use the JavaScript functio ns in the Go o gle Maps API to add a map to o ur page. Mo dify rando m .js as
sho wn:
CODE TO TYPE:
window.onload = init;
function init() {
var submit = document.getElementById("submit");
submit.onclick = getThought;
}
function getThought() {
var aThought = document.getElementById("aThought").value;
if (aThought == null || aThought == "") {
alert("Please enter a thought with at least one word");
return;
}
var id = (new Date()).getTime();
var thought = new Thought(id, aThought);
addThoughtToPage(thought);
}
function addThoughtToPage(thought) {
var ul = document.getElementById("thoughts");
var li = document.createElement("li");
li.setAttribute("id", thought.id);
li.appendChild(spanText);
ul.appendChild(li);
}
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
var mapDiv = document.getElementById("map");
mapDiv.innerHTML = "I'm thinking at " + latitude + ", " + longitude;
if (!map) {
showMap(latitude, longitude);
}
}
function locationError(error) {
var errorTypes = {
0: "Unknown error",
1: "Permission denied by user",
2: "Position not available",
3: "Request timed out"
};
var errorMessage = errorTypes[error.code];
if (error.code == 0 || error.code == 2) {
errorMessage += " " + error.message;
}
console.log(errorMessage);
alert(errorMessage);
}
Save it, o pen rando m .ht m l again, and click . Enter a tho ught and click Subm it . A map appears!
Let's lo o k at ho w we added the map in a bit mo re detail:
OBSERVE:
First, we create a glo bal m ap variable to ho ld the map. We want o nly o ne m ap o bject, so we're just go ing to create it
o nce, sto re it in this glo bal variable, and check each time we add a new tho ught to make sure we're using that same
map.
When the bro wser calls ge t Lo cat io n() with yo ur po sitio n, we check to see if the m ap o bject has been created yet. If it
hasn't, we call sho wMap() to create the map. We pass the lat it ude and lo ngit ude o bjects we retrieved fro m the
po sit io n o bject to sho wMap() and then use tho se to create the map.
The sho wMap() functio n uses the Go o gle Maps API to create a go o gle .m aps.Lat Lng o bject fro m the latitude and
lo ngitude we passed in to the functio n. We then use that go o gle .m aps.Lat Lng o bject to create a set o f o ptio ns we'll
use to create the actual map; these o ptio ns tell the map things like the zo o m level (ho w zo o med in o r o ut yo u are o n
the map; the higher the number, the clo ser the zo o m), where to center the map, and the type o f map yo u want
(ROADMAP, SATELLITE, TERRAIN, o r HYBRID).
We then get the "map" <div> in o ur HTML and use that, alo ng with the mapOptio ns, to create a go o gle .m aps.Map
o bject, which we sto re in the m ap glo bal variable. Finally, we center the map o n the latitude and lo ngitude, by calling
the m ap's panT o () metho d.
No te that this co de is o nly called the first time yo u add a tho ught because we want to create the map just o nce. The
next time yo u add a tho ught, this co de is skipped.
No w, if yo u're sitting at yo ur desk and no t mo ving aro und, all yo ur tho ughts will have the same lo catio n, so yo ur map
wo n't mo ve. But if yo u are running this app o n yo ur smart device and mo ving aro und, then each tho ught will have a
different lo catio n and yo u'll see the map pan to a different lo catio n each time yo u add a new tho ught fro m a different
po sitio n. If yo u have a device and can go mo bile, give it a try! Just enter the URL o f yo ur applicatio n at
o reillystudent.co m into the mo bile bro wser. It will be so mething like this:
.
.
.
function getLocation(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
if (!map) {
showMap(latitude, longitude);
}
addMarker(latitude, longitude);
}
.
.
.
Save it, o pen rando m .ht m l again, and click . No w when yo u add a new tho ught and it appears o n the
map, yo u see a nice red marker sho wing yo u exactly where yo u were when yo u added the tho ught. Again, if yo u add
multiple tho ughts at yo ur desk, yo u'll see o nly o ne marker, because all the markers will sit right o n to p o f each o ther
because yo u aren't mo ving aro und.
To add a marker, we call a new functio n, addMarke r(), fro m ge t Lo cat io n(), passing in the latitude and lo ngitude:
OBSERVE:
addMarke r() creates go o gle Lat Lo ng and go o gle .m aps.Marke r o bjects. In the m arke rOpt io ns, we give the
marker o bject the po sitio n where it sho uld be lo cated, the map to which it sho uld be added, and a title co ntaining so me
text.
Ano ther actio n-packed lesso n! In this lesso n, yo u learned the basics o f the Geo lo catio n and Go o gle Maps APIs, and used
these APIs to create a nice little applicatio n that lets yo u add tho ughts to a web page, and a map. If yo u're able to test the
applicatio n o n a mo bile device, we highly reco mmend it, so yo u can see yo ur tho ughts appear o n the map in different lo catio ns.
Practice yo ur Geo lo catio n and mapping skills a bit befo re mo ving o n to the final lesso n. Hang in there; yo u're almo st do ne!
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Dates and Date Formatting
Final Project
In yo ur final pro ject, yo u're go ing to add mo re features to the To -Do List applicatio n based o n what yo u've learned in
the previo us few lesso ns.
Start with the co de fro m yo ur pro ject fro m the Strings lesso n. Yo ur co mpleted To -Do List applicatio n sho uld suppo rt
the fo llo wing features:
Use Lo cal Sto rage (no t Ajax) fo r sto ring to -do items.
Suppo rt deleting to -do items.
Suppo rt marking to -do items as do ne.
Suppo rt a basic text search o ver the "task" and "who " fields (so I can search by perso n o r wo rd in task).
Suppo rt fo r dates, sho wing ho w many days until a task is due, o r ho w many days o verdue a task is.
Use Exceptio n handling fo r po tential erro rs in Date pro cessing.
Suppo rt Geo lo catio n, so a task has a lo catio n asso ciated with it.
Use Mo dernizr to separate Lo cal Sto rage and Geo lo catio n co de fro m the main co de. Yo ur applicatio n
sho uld still functio n pro perly (altho ugh, o bvio usly, with less capability) even if Lo cal Sto rage and
Geo lo catio n are no t suppo rted.
Do cument yo ur co de by adding co mments explaining what yo u're do ing and why. Submit all yo ur files o nce yo u have
the applicatio n wo rking, including:
Yo ur HTML file.
Yo ur CSS file.
Yo ur JavaScript files (yo u'll have five files).
See the pro ject instructio ns fo r initial co de to help yo u get started. Go o d luck! As always, be sure to email yo ur
instructo r at learn@o reillyscho o l.co m if yo u need additio nal guidance.
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.