Introduction to JavaScript for Java Programmers
Peter Coxhead
Contents
0 JavaScript covered in this handout
1 Introduction
2 JavaScript and Java
3 Data Types in JavaScript
4 Numbers
5 Strings
6 Booleans
7 Objects (includingdocument,Date)
8 Arrays
9 Functions
10 Equality Tests
11 Scope and Existence
12 Events and Event Handling
13 Creating Objects
14 Manipulating CSS
15 Manipulating the HTML DOM
16 Loading and processing XML
References/Bibliography
0 JavaScript covered in this handout
This handout is concerned with JavaScript as used in HTML pages for display in a web browser -- so-called 'client-side JavaScript'. Be aware that there are other uses of JavaScript for which statements written here may not be true; throughout by 'JavaScript' I mean only 'web browser JavaScript'.
For programmers with a knowledge of Java, learning the core JavaScript language is reasonably straightforward. Learning to use JavaScript effectively is an entirely different matter. The language itself is evolving; browsers have their own idiosyncratic implementations; libraries and other sources of code are constantly being updated. The solution is to use the web. For example, the easiest way to check the syntax of the switch statement in JavaScript is to put "javascript syntax switch" into a search engine. To deal with browser differences, try "javascript browser compatibility". There are a number of good tutorial websites on JavaScript, among them http://www.howtocreate.co.uk/tutorials/javascript/, by Mark Wilton-Jones.
The JavaScript described here is designed to work with Firefox, Version 2 onwards, and other so-called 'fifth generation browsers'.
By its nature, client-side JavaScript is freely open to inspection and copying. Don't re-invent the wheel, but do acknowledge any code you re-use and observe any stated restrictions. Note however that JavaScript on the web is not always written in a style which should be emulated.
1 Introduction
The JavaScript language dates back to 1996 and the release of Version 2 of the Netscape browser. It offered two key features:
- When included in HTML pages, it was executed by the Netscape browser, thus allowing web pages to have dynamic features.
- A page displayed by the Netscape browser was parsed in such a way that an internal 'document object model' (DOM) was created which was accessible to the JavaScript language since it mapped HTML elements to JavaScript objects.
Netscape did not intend JavaScript to be a proprietary standard, and so passed the language to the European Computer Manufacturers Association (ECMA) for standardisation. This resulted in a language called ECMAscript, although the name never caught on. The ECMA standard defined the core syntax of JavaScript, but did not fully define the JavaScript DOM.
JavaScript then got caught up in the 'browser wars', when Microsoft decided that a web browser was too important a piece of software to be left to independent companies and, whether intentionally or not, set about driving Netscape out of business. The result has been a very complex and tangled history whereby different browsers implement:
- different versions of JavaScript (e.g. Microsoft's JSoft version with its own extensions)
- different JavaScript DOMs.
The variations in JavaScript are smaller and hence usually less important than the variations in DOMs. The latest browsers show some degree of convergence, but so long as users employ older versions, JavaScript authors face serious problems in creating compatible web pages. The issue will largely be avoided in this introductory module. The best way to find information on this rapidly changing subject is to use a search engine: a good starting point is "javascript browser compatibility".
Returning to the two key features introduced by Netscape, it's important to be clear from the start that both are essential to the successful use of JavaScript.
For example, suppose we want to display an image in a web page, and then change the image when the user clicks on it. Here's a simple XHTML page which does this.
Example 1-0 (http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg1-0.html)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type"content="text/html; charset=ISO-8859-1"/><title>Example 1-1</title><script type="text/javascript">var pic=new Array()pic[0]=new Image(); pic[0].src='image1.jpg'pic[1]=new Image(); pic[1].src='image2.jpg'var i=0 // current imagefunction step(imgNo){ i++if(i>1) i=0window.document.images[imgNo].src = pic[i].src}</script></head><body><p>Click on the image to change it.</p><p><img src="image1.jpg" alt="a clickable image"height="198" width="144" onclick="step(0)"/></p></body></html>
Two things to note first about this example are:
- It is essential that it is served by the web server as "text/html" rather than, for example, "text/xml". Browsers will only attempt to execute JavaScript when presented with an HTML document. The web server may or may not use the meta element to determine how the document should be served. More usually the file extension is the crucial factor: files with extensions such as .html or .htm are served as "text/html", files with the extension .xml are served as "text/xml".
- When JavaScript is embedded into an XHTML document, as here,
characters such as
<or&make it not well-formed. At present, there's no satisfactory way of dealing with this problem which is also reasonably browser-safe.[1] The best solution seems to be to put as much JavaScript as possible into external files.
Adopting this approach, we can re-write Example 1-0 as follows.
Example 1-1 (http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg1-1.html)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type"content="text/html; charset=ISO-8859-1"/><title>Example 1-1</title><script type="text/javascript" src="Eg1-1.js"></script></head><body><h1>Example 1-1</h1><p>Click on the image to change it.</p><p><img src="image1.jpg" alt="a clickable image"height="198" width="144" onclick="step(0)"/></p></body></html>
Code loaded from a file via the src attribute of a
script element is treated exactly as
if it was written out in full in the same place. Notice that I deliberately
didn't use the empty element form <script ... />. Although
this should have exactly the same meaning, many browsers at present don't handle
such forms properly.
The file Eg1-1.js then contains the JavaScript:
var pic=new Array()pic[0]=new Image(); pic[0].src='image1.jpg'pic[1]=new Image(); pic[1].src='image2.jpg'var i=0 // current imagefunction step(imgNo){ i++if(i>1) i=0document.images[imgNo].src = pic[i].src}
The other piece of JavaScript in the example is the value
step(0) of the onclick attribute of the
img element.[2] This has
to remain embedded in the HTML document, but won't cause a validity problem as
it doesn't contain any characters which are invalid in XML/XHTML. In general,
it's better to put as little JavaScript as possible into the body of an HTML
page. A useful rule of thumb is to have only a single JavaScript statement in
attribute values or script elements in the body; more
than this should be moved to an external file and put into a function to be
called from the body. It's much harder to understand (and hence debug) both the
HTML and the JavaScript when they are mixed up.
Wherever it's placed, the browser processes JavaScript sequentially as the
page is loaded. JavaScript statements which are 'free' (i.e. not inside a
function declaration) are executed as they are processed. So before the
body element is processed, an array pic
will have been set up with two elements of type Image (a predefined
object type), each with its src field set to the name of a JPG
image file. Setting src causes the browser to load the image. The
variable i (representing the index of the current image) will have
been set to 0.
Functions, indicated by the function keyword, are parsed and
stored, but not executed. This enables functions to refer to things which
haven't yet been created, whereas executed code cannot. For example, immediately
after
var i=0 // current image
it would be syntactically correct to write
window.document.images[0].src = pic[i].src
However, the browser would attempt to execute this statement, which would
result in an error, since although i has the value 0 and
pic[i] exists, window.document.images[0] does not yet
exist -- it is only created by the browser when it reaches the first
img element in the body of the
HTML.
After the page has been displayed, the DOM becomes relevant. The browser must
know that the img element has an
onclick attribute, whose value is to be executed when
the user clicks on the image. Execution here calls the function
step with imgNo given the value 0 (meaning the first
image in the document).
The DOM is needed again to ensure that the expression
window.document.images[imgNo] inside the function
step[3] refers to the JavaScript
object corresponding to the first img element in the
web page. The browser window is represented as the window object.
Its document field represents the content of the browser window;
this in turn has a field images which is an array of all the images
(img elements) in the HTML. Each image is represented
by an object of type Image; such objects have a field
src which gives the URL of the image file.
A better style than using the images array is to use the
getElementById method of the document object. Given a
string as its argument, this returns the element of the document whose
id is equal to that string. This avoids relying on a
detailed knowledge of the order of images in the document and also of the DOM
hierarchy.[4]
It's unusual to click on an image to change it; a further improvement is to use a button, since this is a more obvious way of achieving an action. Both revisions are shown below.
Example 1-2 (http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg1-2.html)
JavaScript change:
function step(imgID){ i++;if(i>1) i=0document.getElementById(imgID).src = pic[i].src}
XHTML change:
<body><p><img id="i01" src="image1.jpg" alt="a changeable image"height="198" width="144" /> <br/><input type="button" value="Next" onclick="step('i01')" /></p></body>
2 JavaScript and Java
I don't intend to present a complete overview of even the latest version of JavaScript, let alone earlier versions. As always the web is the best source of up-to-date information.
As its name suggests,[5] JavaScript has a similar syntax to Java. For programmers familiar with Java, this is both an advantage and a disadvantage.
- It's an advantage because it's not necessary to learn the
detailed syntax of constructs such as
ifstatements, loops, etc. Much JavaScript code is exactly the same as it would be in Java. - It's a disadvantage because JavaScript is a fundamentally different language to Java.
Some differences between Java and JavaScript syntax:
- JavaScript allows statements to be terminated by a newline, so does not require semi-colons after every statement. (On the other hand, it is generally considered good programming practice to include the semicolons and I will do so in future examples.) Care is needed when splitting statements between lines, since if each of the lines is separately valid, JavaScript will treat them as separate statements.
- Although JavaScript does have objects and the equivalent of
classes, their syntax is significantly different. In particular JavaScript does
not require an 'outer' class, as we have seen in the example above, nor any
special
mainmethod. Many JavaScript programs contain no overt classes at all, simply consisting of function definitions and statements (including function calls) embedded in an HTML page. - The keyword
functionis needed when defining functions (the equivalent of methods in Java). - Because JavaScript is not a strongly typed language,[6] declarations of variables and function arguments do not include type information.
- The rules defining the scope of variables are different. JavaScript does not have 'block level scope'. Java programmers need to take care not to inadvertently over-write existing variables when writing JavaScript.
- The set of 'reserved words' which cannot be used as
identifiers is somewhat different: see e.g.
http://javascript.about.com/library/blreserved.htm.
Further, since global variables are actually fields of the
windowobject, care must be taken not to over-ride existing fields. For maximum compatibility, values of id attributes in the HTML should not be used as identifiers.
3 Data Types in JavaScript
A major difference between JavaScript and Java is that JavaScript variables are not of a fixed type. In particular:
- Variables are not confined to one type. Variables should
normally be declared using the keyword
var. Either when declared or later, they can be assigned values of any valid type. Thus the following is legal in JavaScript:
var count = 0; // declared and initialized to a number valuecount = count + 1;count = 'Error: too many values entered.'; // given a string value
- During execution, JavaScript values are automatically converted from one type to another to ensure type compatibility. Usually this has the expected effect, but care is needed. Consider this code:
var product = 2 * '10' + 3;var sum = 2 + '10' + 3;var msg = 'Distance is '+(2+3)+' km';
This results in
producthaving the numerical value 23, since the string'10'is converted to a number to be compatible with the*operator. Howeversumhas the string value'2103', since, as in Java, the+operator is overloaded and is assumed to refer to string concatenation if either of its arguments is a string. The parentheses are essential if the value ofmsgis to be 'Distance is 5 km'; if they are omitted the value will be 'Distance is 23 km'.
JavaScript has three non-composite data types: numbers, strings and booleans, and one composite type: object. Arrays are a special kind of object, as are functions.
As in Java, the distinction is important when considering the effect functions can have on the values of their arguments:
- If a variable has a non-composite data value, then if it is used as the argument of a function, its value outside the function cannot be changed.
- If a variable has a composite data value, then changing a component of that value inside a function changes the value outside the function.
Consider the following schematic code (where material in angle brackets isn't JavaScript).
function test(a,b){ // Point 1a = <new value of any type>;// Point 2b<component identifier> = <new value of any type>;// Point 3}var x = <non-composite value>;var y = <composite value>;test(x,y);
At 'Point 1', both the global variable x and the parameter
a refer to the same value. At 'Point 2', a is changed
to refer to a new value, but this does not change the value to which
x refers.
At 'Point 2', both the global variable y and the parameter
b refer to the same composite value. However, between 'Point 2' and
'Point 3', the content of what both y
and b refer to is changed. At 'Point 3', y and
b still refer to the same value but that value is now different as
it has a changed component.
4 Numbers
JavaScript does not distinguish between integers and reals. All numbers are represented in 8 byte IEEE floating point numeric format, regardless of how they are written in the program. It is important not to rely on exact integer arithmetic when writing JavaScript. For example, it is dangerous to test computed numerical values for equality, even if it appears that only integer operations have been carried out.
IEEE floating point numbers include values displayed in JavaScript as
Infinity, -Infinity and NaN ('Not a
Number').
var result = 2 / 0; // result has the value Infinityresult = -2 / 0; // result has the value -Infinityresult = 0 / 0; // result has the value NaN
JavaScript has many predefined objects and classes, among them
Number and Math.
Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY and
Number.NaN provide another way of creating the three special
numerical values. Note that although equality tests work for
Infinity and -Infinity, they do not for
NaN. The built-in function isNaN must be used.
2 / 0 == Number.POSITIVE_INFINITY // has the value true-2 / 0 == Number.NEGATIVE_INFINITY // has the value true0 / 0 == Number.NaN // has the value falseisNaN(0 / 0) // has the value true
The Math 'class' gives access to a wide range of mathematical
constants and functions. For example, Math.sqrt(x) calculates the
square root of x.
5 Strings
Strings in JavaScript are similar to those in Java, with some exceptions:
- JavaScript does not have a character type, so there is no
distinction between
'a'and"a"; both are ways of writing the same string. The word don't can be represented in JavaScript as"don't"or as'don\'t'. A useful convention is to use double-quotes for HTML attribute values and single-quotes for JavaScript strings. - The
==operator can be used with strings. Given the code
var s1 = 'Hello!';var s2 = 'Hello!';var s3 = s1;
all of the following expressions are true:
s1 == s2s1 == s3s3 == 'Hello!'
More surprisingly perhaps the expression
3 == '3'is also true because JavaScript applies type conversion to the operands of the==operator; equality and identity operators are discussed further in Section 10 below.
- All the comparison operators work with strings; type
conversion will be applied so that the expression
'Aa' > 'AA' && '30' > 30-1is true.
6 Booleans
JavaScript has the boolean values true and
false.
Basic and C programmers will not be surprised to learn that the expression
10 * true - 3 * false has the value 10, since true and
false are converted to 1 and 0 in this context.
Never take advantage of this in writing code!
In type conversions, 0, '', null and
undefined are all treated as false, all other values as true.
Again, never take advantage of this!
7 Objects (including document, Date)
JavaScript is an object-oriented language but handles objects very differently from Java. Strictly speaking JavaScript does not have classes and class hierarchies in the Java sense; it has 'prototype-based inheritance' rather than 'class-based inheritance'. However, it's convenient to use the word 'class' when discussing JavaScript constructs that parallel those in Java.
Many of the objects used in JavaScript are created by the browser. In the
earlier example, we saw that the window object was the top level
JavaScript object, representing the browser window (assuming there are no
frames). Where there would otherwise be no ambiguity, a reference to the top
level window object can be omitted. Thus instead of the full
expression window.document we can just write
document.
The document object represents the HTML document as displayed in
the browser window. Among many other fields, it has a field images
which is a variable length array of images (each of 'type'
Image).
Installing Firebug in Firefox makes it easy to examine the fields and methods of predefined objects. (Firebug is available from http://getfirebug.com/.) For example:
- Open http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg1-1.html in Firefox.
- Open Firebug (Tools menu).
- Set the tab in the left-hand
panel to show 'HTML' and the tab in the right-hand
panel to 'DOM', choosing 'Show DOM Properties' from the 'Options' menu which
appears to the extreme right. The left-hand panel will now show the structure of
the HTML proper; selecting an item in this panel will show the structure of the
corresponding JavaScript object. For example, with the img element selected in
the left-hand panel, the right-hand panel shows the structure of the
corresponding
Imageobject (as generated by Firefox -- other browsers may differ!). - Alternatively, select the 'DOM' tab to the left and a single
panel will be shown. This starts with the top-level
windowobject which can then be explored.
'Console logging' in Firebug is also extremely useful in developing and debugging JavaScript. See http://getfirebug.com/logging.html.
JavaScript offers two methods of accessing the fields of objects.
- The standard Java-style syntax can be used. Thus
document.imagesaccesses theimagesfield of thedocumentobject. - Objects can be treated as if they were arrays, with a string
representing the name of the field used as the index. In JavaScript
a.banda['b']are exactly equivalent. Thusdocument.images[1]is the same asdocument['images'][1]. The advantage of this notation is that an arbitrary field can be selected, since the 'array index' can be a variable. Thusa[x]selects the field of objectawhose name is the string value ofx(assuming that such a field exists).
A typical application of JavaScript is to change the fields of
browser-created DOM objects. For example, if the function step in
Example 1-2 above is changed to the following:
function step(imgID){ i++;if(i>1) i=0;var theImg = document.getElementById(imgID);theImg.width += 10;theImg.src = pic[i].src;}
then each time the button is clicked, not only does the image change, but it
also becomes wider and more distorted. (See
http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg1-3.html.)
Objects can have methods (i.e. functions). A useful method of the
document object is write. When executed it inserts
output into the HTML source at the point at which it appears. This will only
cause a change in the page displayed if the generated HTML is then processed by
the browser. (Although document.write is easy to use, there's no
way of validating the (X)HTML it generates. For this reason, many browsers don't
support document.write in pages served as XML or XHTML. A better
approach, although it's more complex, is to use functions which add to or alter
the underlying DOM, as discussed in Section 15. Such
functions are designed to make it impossible to create invalid output.)
XHTML 1.0 Strict has some important restrictions on where
script elements, and hence document.write,
can be placed. For example, table and
tr elements cannot contain
script elements, so a document.write can
only generate either a whole table or the contents of a single cell.
Suppose we change the body in Example 1-2 above to:
<body><p><img id="i01" src="image1.jpg" alt="a changeable image"height="198" width="144" /> <br/><button onclick="step('i01');">Next</button></p><p><script type="text/javascript">document.write('Currently showing image '+i);</script></p></body>
We might hope that this would display a message saying which image is
currently being shown. If so, we will be disappointed. The text 'Currently
showing image 0' will be inserted into the HTML once and once only, namely the
first time that the JavaScript is executed. (Re-loading the page doesn't change
this, because it will cause the JavaScript in the header to be re-executed,
setting i back to 0.) Section 12 below shows
one way of achieving dynamic messages.
JavaScript has the built-in ability to create 'date' objects, which make
handling dates and times easier. When initially constructed, a new
Date object will hold the current
date and time:
var now = new Date(); // Holds current date and timevar dayInMonth = now.getDate(); // Day of the month, from 1 to 31var dayInWeek = now.getDay(); // Day of the week, from 0 (Sun) to 6 (Sat)var month = now.getMonth(); // Month, from 0 (January) to 11 (December)var year = now.getFullYear(); // Year as a 4 digit numbervar hr = now.getHours(); // Hour, from 0 to 23var min = now.getMinutes(); // Minute, from 0 to 59var sec = now.getSeconds(); // Second, from 0 to 59var timeStr = 'Time now is '+hr+':'+min+':'+sec+' on '+dayInMonth+'/'+(month+1)+'/'+year;
A web page which displays the above 'time string' using the
document.write method will be found at
http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg7-1.html.
Date objects store time internally as milliseconds since
midnight, 1 January 1970. Using the getTime and
setTime methods which access these values, it's possible to step
backwards and forwards from a given date and time, without having to deal with
the complexities of the varying number of days in a month or whether it's a leap
year or not.[7] The code fragment below sets
tomorrow to exactly one day from now:
var now = new Date();var tomorrow = newDate();tomorrow.setTime(now.getTime()+24*60*60*1000);
For an example of the use of date calculations, see http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg7-2.html.
It is also possible to create your own objects and 'classes' of object in JavaScript; see Section 13.
8 Arrays
Arrays are a kind of object. Unlike Java arrays, they are not of fixed size and are untyped. Thus the following is legal:
var a = Array();a[0] = 6;a[2] = ' is ';a[3] = true;
The value of a[1] is undefined. (The expression typeof
a[1] will have the string value 'undefined'.) The length of
the array, as determined by a.length, is 4.
Had a been declared by
var a = Array(5);
with the same statements following, then a[4] would also be
undefined. It is not an error to attempt to access undefined values in
JavaScript; they generally behave as if they had the value null
(similar to Java's null). We can show all the defined values in an
array via code such as:
function showArray(a){ var msg = '';for (var i=0; i<a.length; i++){ if(a[i] != null) msg = msg + a[i];}alert(msg);}
Notice the use of the function alert (strictly
window.alert). This takes a single string argument and displays an
alert box containing it. In this case the message will be '6 is true'. Had
if(a[i] != null) been omitted, the message would have been
'6undefined is trueundefined'.
(JavaScript's automatic type conversion means that we could just write
if(a[i]) msg = msg + a[i] + '\n';
since the value of a[i] will be converted to true or false as
required. Avoid this style! The values 6, ' is ' and
true contained in a will indeed be treated as true,
while null or undefined values are treated as false. However, if
a[0] were set to 0 instead of 6, this would also be treated as
false and hence omitted from the output. Although this style is commonly used by
JavaScript authors, my advice is to avoid it.)
'Array literals' -- values enclosed in square brackets and separated by commas -- can be used wherever arrays can. Their main use is in initialization:
var a = [6, , ' is ', true]; // a[1] is undefined.
A number of methods are predefined for arrays. These include
concat, reverse and sort. By default the
latter sorts arrays alphabetically, converting if necessary. Thus the following
code generates the message ' is 6timetrue'.
var a = [6, 'time', ' is ', true];a.sort();var msg = '';for (var i=0; i<a.length; i++){ if(a[i] != null) msg = msg + a[i];}alert(msg);
9 Functions
We have seen that functions can be defined in JavaScript using the keyword
function. An important difference from Java is that in JavaScript,
functions are a data type just as are numbers or strings. The following
definitions of the function square are equivalent:
function square(x) { return x*x; }var square = function (x) { return x*x; }
Each definition gives the variable
square a function value. Function values can be assigned like any
other value. For example, this code displays the value 27:
function cube(x) { return x*x*x; }cubeOf = cube;alert(cubeOf(3));
JavaScript functions can be called without their full number of arguments -- see the further discussion in Section 11 below. Parameters are matched to arguments from the left; any unmatched parameters are given the special undefined value.
It is extremely important to understand the difference between these two lines of code:
cubeOf = cube; // cubeOf is now a function.cubeOf = cube(); // cubeOf now has the value NaN.
The first assigns the value of the variable cube (which is a
function) to the variable cubeOf, so that cubeOf is
effectively the same function as cube. The second calls the
function cube, which will return NaN (since
cube's parameter x will be given the undefined value)
and then assigns this value to cubeOf.
This feature of JavaScript allows function values to be passed as arguments.
For example, the function map(f, a) defined below returns the array
formed by applying the function f to each of the elements of the
array a.
function map(f, a) // f is a function, a is an array{ var res = Array();for (var i=0; i<a.length; i++){ res[i] = f(a[i]);}return res;}var a = [3, 10, -1, 5];showArray(map(cube, a)); // showArray() was defined earlier
The values displayed will be 27, 1000, -1, 125.
10 Equality Tests
The JavaScript equality operator == applies automatic type
conversions to its arguments; its opposite is !=. Numbers, strings
and booleans are then equal if they have equal values; even objects may test as
equal if there are valueOf or toString methods which
can be used to convert them to numbers, strings or booleans. Thus in the
following JavaScript code, all of the values successively assigned to the
variable test will be true:
var s = 'This';var test;var x;var y = new Object();y.val = 'This';y.toString = function() { return y.val }test = 'This' == s; // Operands of same type; no type conversion.test = 10 == '10';test = false == 0;test = x == null; // Note that x exists but is undefined.test = y == s; // Its toString() method is applied to y.
JavaScript has another operator, ===, which does not apply type
conversions to its arguments; its opposite is !==. If in the code
above, == is replaced by ===, only the first
assignment to test yields true; all the others yield false.
11 Scope and Existence
The rules governing the scope of a variable -- roughly the region of the program in which it is available -- differ very significantly between JavaScript and Java.
The scope of a JavaScript variable defined by a var statement
is:
- The whole program, i.e. all the
JavaScript used in the web page regardless of where it appears, if the
varstatement appears outside a function definition. - The whole of the function if the
varstatement appears in the function.
There is no block level scope in JavaScript.
If a variable is assigned a value but there is no corresponding
var statement, then the variable is always implicitly declared
globally. This can cause considerable confusion. You are strongly advised always
to declare variables with a var statement either globally or
locally to a function.
Consider the following convoluted code (which should not be imitated!):
var i = 'global';var j = 'global';function display(i,j,k,l){ var msg = 'i = '+i+'\n';msg += 'j = '+j+'\n';msg += 'k = '+k+'\n';msg += 'l = '+l+'\n'; // Point 1alert(msg);}function test(i){ i = 'test';var j = 'test';k = 'test';l = 'test';}display(i,j,k); // Point 2test(i);display(i,j,k,l); // Point 3var k = 'global';
This code demonstrates a number of features of JavaScript scoping:
- Functions can be called with the wrong number of arguments,
as in the first call of the function
displayabove. Matching works from the left; un-matched parameters are given the undefined value. Hence the alert at 'Point 2' above shows thatiandjhave the value 'global', whereaskandlare displayed as 'undefined'. - There is a distinction between a variable's existence and its
value being undefined. At 'Point 1' above, the variable
lexists (because it has been specified as the parameter of the functiondisplay). However, in the first call ofdisplayit doesn't have a value, so is given the special undefined value. If we change the statement at 'Point 2' above to
display(i,j,k,l); // Point 2
then at this point
ldoes not exist at all, since it has not been declared in avarstatement nor has it been assigned a value (nor is it a function parameter). Hence a JavaScript error will be generated.(Safari [2.0.4] produces the appropriate error message 'ReferenceError -- can't find variable: l'. Firefox [2.0.0.1] produces the error message 'l is not defined' which is potentially misleading, since the error is not caused by
lhaving the undefined value.)You may wonder why the variable
kexists at 'Point 2' but has the undefined value. Since scope extends backwards, the statementvar k = 'global'at the end of the program has exactly the same meaning as writingvar kat the very top of the program and thenk = 'global'at the end. The existence ofkextends backwards; the assignment of a value does not.
- Inside the function
test,iandjare respectively a parameter and a locally declared variable. Hence assigning them string values does not change the global variables of the same name. The variablekis not declared insidetest, but has been declared globally, sokis the global variable. Hence assigning it a string value changes the global variable. The variablelis not declared anywhere in the program, so the statementl = 'test';is an implicit global declaration executed when the function is first executed.
The result is that at 'Point 3' in the program the values of
iandjare still the string 'global', whereaslnow exists and along withkhas the value 'test'.
The moral should be clear: don't write code like this!
- Declare global variables at the top of the program with an
explicit
varstatement, initializing them where appropriate. - Declare local variables in the same way.
Since JavaScript has no block level scoping, Java programmers need to be very
careful with loop variables. In code like the following, it is easy to forget
that in JavaScript the loop variable i is actually the global
variable i, in spite of its apparently separate declaration:
var i = 6;...for (var i=0; i<10; i++) // The var is redundant and has no effect{ ... }// At this point, i has the value 10 not 6.
JavaScript programmers frequently want to test whether some entity exists and/or has a value at a particular point in a program.
- The only test that is
always safe for a variable is whether the
typeofoperator does not return the string'undefined', e.g.typeof x != 'undefined'. This yields true if the variable exists, even if it has not been given a value, otherwise it yields false. - Fields of
existing objects can also be tested for inequality
with
null, e.g.window.x != null. This yields true if the field exists and has been given a value other thannull. Referencing a non-existent object will raise an exception; referencing a non-existent field does not (provided its object exists). Hence the use ofwindow.xhere rather thanx. - A direct test on a field, as in
if (window.x), which is commonly seen, yields true if the field exists and has been given a value other thannull, false, the empty string or 0.
12 Events and Event Handling
We have already seen the onclick attribute used with both images and buttons (Examples 1-1 and 1-2 above). When an item with this attribute is clicked by the user, by default the text value of the attribute is treated as JavaScript and is executed by the browser.[8] In general the value will contain a function call, although any valid JavaScript is equally possible.
There are some 20-odd predefined events with corresponding HTML attributes, although not all will have event handlers supported by all browsers for all HTML elements.
It's important to distinguish between the action taken by the event handler and the default action taken by the browser. Consider the following example.
Example 12-1 (http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg12-1.html)
JavaScript:
function check(){ return confirm('Go to the School of Computer Science web site?');}
HTML:
<p><a onclick="return check();"href="http://www.cs.bham.ac.uk">School of Computer Science</a></p>
When the user clicks on the link, the browser first executes the JavaScript
return check(). The function check is called and uses
the function confirm (a method of window) to display a
dialog asking the user to choose between the ok and cancel buttons. The value
returned by confirm is one of the boolean values true or false,
which in turn is the value returned by check. The result is that
the onclick attribute returns true or false to the
browser. For this event handler, false causes the default browser action to be
abandoned, so the link is not followed.
Five of the most useful general events and handlers are shown in the table below.
| Event | Cause | Effect of a return value |
| onchange | User finishes entering text in a text box or selects/deselects an item. |
|
| onclick | User clicks once. | If a link, false prevents it being followed. |
| onload | Item finishes loading (document or image normally). |
|
| onmouseover | Mouse moves over item. | If a link, true prevents URL being displayed in the status bar. |
| onmouseout | Mouse moves off item. |
|
Example 12-2 uses all of these five events to create a web page which can evaluate a simple arithmetic expression consisting of two numbers and an operator. I suggest you look at the web page in operation before studying the source code and reading the notes below it.
Example 12-2 (http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg12-2.html)
JavaScript:
var xBox; var yBox; var zBox; var msgBox;// Initialize the global variables to the five text boxesfunction init(xID, opID, yID, zID, msgID){ xBox = document.getElementById(xID);opBox = document.getElementById(opID);yBox = document.getElementById(yID);zBox = document.getElementById(zID);msgBox = document.getElementById(msgID);}// Reset the boxesfunction reset(){ xBox.value = 0;yBox.value = 0;zBox.value = 0;opBox.value = '+';msgBox.value = '';}// Calculate and display the result of x op yfunction calculate(){ var x = parseFloat(xBox.value);xBox.value = x;var y = parseFloat(yBox.value);yBox.value = y;var op = opBox.value;switch(op){ case '+': z = x + y; break;case '-': z = x - y; break;case '*': z = x * y; break;case '/': z = x / y; break;default: opBox.value = '+'; z = x + y; break;}zBox.value = Math.round(1000*z)/1000;}// Show a message in the msgBoxfunction msg(msgText){ msgBox.value = msgText;}
HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type"content="text/html; charset=ISO-8859-1"/><title>Example 12-2</title><script type="text/javascript" src="Eg12-2.js"></script></head><body onload="init('left', 'op', 'right', 'res', 'note');"><h1>Simple JavaScript calculator</h1><p><input id="left" type="text" size="7" value="0"onchange="calculate();"onmouseover="msg('Input a number.');"onmouseout="msg('');"/><input id="op" type="text" size="2" value="+"onchange="calculate();"onmouseover="msg('Input one of +, -, *, /.');"onmouseout="msg('');"/><input id="right" type="text" size="7" value="0"onchange="calculate();"onmouseover="msg('Input a number.');"onmouseout="msg('');"/>=<input id="res" type="text" size="15" value="0" disabled="disabled"/><input type="button" value="Reset"onclick="reset();"/><br/><input id="note" type="text" size="30" value="" disabled="disabled"/></p></body></html>
Some notes on this example:
- In the interests of modularity it is desirable to de-couple
HTML and JavaScript code as far as possible. In particular, it is much easier to
make changes if the values of
idattributes are not 'hard coded' into the JavaScript. One way of achieving this is to use an initialization function, likeinitin Example 12-2, which passes the necessaryidvalues to the JavaScript program. The onload attribute of the body element causes the corresponding event handler to be called only after the body is fully loaded, thus ensuring that all elements have been created before the JavaScript code attempts to access them. - When the mouse is over the first three
input elements, their
onmouseoverandonmouseoutattributes cause an informative message to appear in the"note"input element; it disappears when the mouse moves away. - The
"res"and"note"input elements are disabled to prevent user input as they are only used to display text. Browsers automatically refresh the contents of form elements when their values change, unlike 'normal' HTML elements which are static once displayed. - The
onchangeattribute of the first three input elements ensures that when the user changes them, the result value is changed via a call tocalculate. The user interface is slightly awkward because the user must move away from the element to trigger the onchange event. In general this is not as useful an event as it may seem. - The value obtained from an input
element is a string. Rather than use automatic type conversion (which will work
only if the syntax of the string is exactly correct), the built-in
parseFloatfunction has been used. This is more tolerant of incorrect input (e.g.'34.1a'will be converted to 34.1). Notice that the value produced after conversion is re-displayed so the user knows what number has been accepted.
13 Creating Objects
There are a number of different ways of creating your own objects in JavaScript. Only one approach is discussed here.
Although you will see examples of JavaScript programming in which objects and constructors are used in what appears to be Java-like fashion, in reality this approach is of considerably less value:
- In Java, classes and class-based inheritance are used to create a compatible and safe set of types. JavaScript's untyped variables make this irrelevant.
- Classes and their instances in Java allow information hiding to be enforced, e.g. by declaring fields and methods to be private. JavaScript objects are completely open. If data is stored in the fields of objects, access methods are only useful when consistency between fields must be maintained.
Individual objects can be created by the new operator applied to
the class Object:[9]
var myObject = new Object();
The required fields and methods of the newly created object can then be set up. They can be added to at any time after the object has been created.
myObject.age = 32;myObject.name = 'John';myObject.toString = function () { return myObject.name+' '+myObject.age; };
Given this code, the statement alert(myObject) will display
'John 32', the toString method being called as a result of
automatic type conversion (alert expects its argument to be a
string).
Typically we want to create more than one object of a kind. One solution is to create a 'factory' or 'make' method that creates and returns objects:
function makeRect(width,height){ var r = new Object();r.w = width;r.h = height;r.toString = function() { return r.w+' by '+r.h; };return r;}rect1 = makeRect(10,45);rect2 = makeRect(100,10);alert(rect1); // Will display '10 by 45'
As noted above, we could create accessors like getWidth or
setWidth, but as there is no way of preventing access to the fields
of the objects, this would produce no real gain at the expense of
inefficiency.[10]
The slight problem with this approach is that every object created by
makeRect will contain identical copies of its methods, which is an
inefficient use of memory. This is only likely to become a problem when creating
a large number of objects with substantial method definitions.
An alternative is to create a single global function and store a reference to
it in the object. Because the variable r will not be available
globally (it is local to makeRect), we need to use the special
variable this which refers to the originating object of a
method:
function _rectToString(){ return this.w+' by '+this.h;}function makeRect(width,height){ var r = new Object();r.w = width;r.h = height;r.toString = _rectToString;return r;}var rect1 = makeRect(10,45);alert(rect1); // Displays '10 by 45'.
(Since _rectToString is now global, it could be called directly;
giving it a name starting with an underscore is a useful convention to show that
it is not meant for independent use.)
Re-use can be achieved by writing a new 'make' method which adds or changes fields based on an existing object type. For example, to create filled rectangle objects, we can start with a rectangle object:
function makeFilledRect(width,height,colour){ r = makeRect(width,height);r.colour = colour;r.toString = function (){ return this.w+' by '+this.h+'; colour '+this.colour;};return r;}var rect2 = makeFilledRect(11,56,'green');alert(rect2); // Displays '11 by 56; colour green'.
JavaScript has 'object literals': a list of field name and value pairs separated by commas. The statement:
r = { w: 10, h: 25, colour: 'green' };
creates an object r with fields w, h
and colour.
14 Manipulating CSS with JavaScript
One of the interesting uses of JavaScript is to manipulate the style of HTML elements dynamically.
JavaScript creates a style object for each element and makes it
a field of the object representing that element. The style object
in turn has fields. Important ones are cssText and fields
corresponding to each applicable CSS property. Capitalization is used in field
names in place of the hyphens in CSS properties. Thus for example
border-left-width becomes borderLeftWidth. Note that
the style object only holds the styles explicitly declared for that element, not
those inherited via class attributes.
Example 14-1 shows a simple example of dynamic CSS. As with earlier examples, I suggest you look at the web page in operation before studying the source code and reading the notes below it.
Example 14-1 (http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg14-1.html)
JavaScript:
var box1;var goRed = true;function changeColour(){ if(goRed) box1.style.borderColor = 'red'else box1.style.borderColor = 'black';goRed = !goRed;}function init(boxID){ box1 = document.getElementById(boxID);setInterval('changeColour()',1000);}
HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type"content="text/html; charset=ISO-8859-1"/><title>Example 14-1</title><style type="text/css">div.box{ border-width: 2px;border-style: solid;border-color: black;width: 100px;height: 100px;}</style><script type="text/javascript" src="Eg14-1.js"></script></head><body onload="init('box1');"><p>The box below should change colour regularly from black to red andback again.</p><div id="box1" class="box"/></body></html>
Some notes on this example:
- The style attached to all elements of class
boxcauses them to have a 2 pixel wide black border all round. In the example, this style is attached to a div with id"box1". - The function
initsets the value of the global variablebox1to the JavaScript object representing the div. ThesetIntervalfunction is then used for animation (see the discussion below);changeColourwill be called once every second. - The function
changeColourthen sets and re-sets theborder-colorstyle specifically for the div element, which by the rules of CSS, over-rides the class-wide style. This relies on the browser dynamically changing its display whenever the CSS changes.
JavaScript provides three useful 'timer' functions (methods of the
window).
setInterval(JavaScript_string, time_in_msec)executes the JavaScript given in the first string argument after every interval oftime_in_msecmilliseconds. It returns a timer identifier which should be saved if it will be necessary to stop the activity.clearInterval(timerID)stops the action created bysetInterval:
timer1 = setInterval('run()', 2000);. . . . . .clearInterval(timer1);
setTimeout(JavaScript_string, time_in_msec)executes the JavaScript given in the first string argument after one interval oftime_in_msecmilliseconds. (If the action has not taken place, it can be stopped viaclearTimeout(timerID).)
In Section 13, we discussed creating objects and in particular rectangle objects. By combining this with the technique discussed here we can 'draw' the rectangles as they are created.
We first set up the CSS box class to ensure that a
div of this class will not be visible:
<style type="text/css">div.box{ position: fixed;top: 0px; left: 0px;width: 0px; height: 0px;border: solid 0px transparent;}</style>
Notice that we declare position: fixed; to ensure that the
element is positioned at an absolute location relative to the
window.[11] Now we write the JavaScript to
create rectangle and filled rectangle objects, defining their size, position and
colour via CSS styles. Note how we store a reference to the corresponding DOM
object within our rectangle object so that it is easy to access the
style field.
function makeRect(id,left,top,width,height){ var r = new Object();r.left = left;r.top = top;r.right = left+width;r.bottom = top+height;r.domObj = document.getElementById(id);r.domObj.style.cssText += 'left:'+left+'px; top:'+top+'px; width:'+width+'px; height:'+height+'px; border: solid 2px black;';return r;}function makeFilledRect(id,left,top,width,height,colour){ r = makeRect(id,left,top,width,height);r.colour = colour;r.domObj.style.cssText += ' background-color: '+colour;}
The function init is used to set up three rectangles when the
body of the page has loaded:
function init(box1ID,box2ID,box3ID){ makeFilledRect(box1ID,15,60,50,100,'green');makeRect(box2ID,30,100,150,75);makeFilledRect(box3ID,45,85,50,50,'red');}
Finally we set up the initially invisible div elements in the body of the page:
<body onload="init('box1','box2','box3');"><div id="box1" class="box"></div><div id="box2" class="box"></div><div id="box3" class="box"></div></body>
The complete page will be found at http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg14-2.html.
If we want the user to interact with our objects, then it is useful to create
links in both directions between our object and the corresponding DOM object.
JavaScript's ability to add fields to existing objects makes this easy to do,
although we must be careful not to over-ride an existing field. The
makeRect function can have an additional line added:
function makeRect(id,left,top,width,height){ var r = new Object();. . . . .r.domObj = document.getElementById(id);. . . . .r.domObj.owner = r; // Relies on 'owner' being a new field!return r;}
As a simple of example of how this link can be used, suppose we want to
change the appearance of a rectangle object when the user clicks on it. We begin
by adding a clicked field and a doClick method to
makeRect:
function makeRect(id,left,top,width,height){ var r = new Object();. . . . .r.clicked = false;r.doClick = function (){ if (!r.clicked) r.domObj.style.borderColor = 'yellow';else r.domObj.style.borderColor = 'black';r.clicked = !r.clicked;};return r;}
Finally we add an onclick event handler to the
div elements. In the event handler code which is the
value of an event attribute, the keyword this refers to the DOM
object corresponding to the HTML element. Hence by using this and
the owner field we have added to it, we can link to the
corresponding rectangle object:
<div id="box1" class="box" onclick="this.owner.doClick()"></div><div id="box2" class="box" onclick="this.owner.doClick()"></div><div id="box3" class="box" onclick="this.owner.doClick()"></div>
The complete page will be found at http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg14-3.html.
For a further example of what it is possible to do with JavaScript and dynamic CSS, see http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg14-4.html. (Depending on your machine and browser, the animation speed may or may not be satisfactory.)
15 Manipulating the HTML DOM
Consider the following XHMTL:
<div><p>Manipulating the JavaScript DOM is a <b>very</b> importanttechnique.</p><p>Varying browser support is the main problem.</p><div>
Like all XML, it can be represented as a tree of nodes. In the following diagram, note that the limited width of the page has prevented me from aligning all the grandchildren of the div correctly; also spaces and newlines are shown explicitly in the values of text nodes.

Usually we don't really want the tree to contain text nodes which represent only 'white space', but we must be prepared for them to be present.[12]
The browser will create a JavaScript object for each node, including the text nodes, as part of the DOM. Each node has some standard fields and methods. To move through the tree, the following fields can be used:
parentNodecontains the parent of the node.childNodescontains an array of the child nodes.firstChild,lastChildprovide immediate access to the first and last child nodes.nodeNameholds the name of the node as a string (e.g.'div','b'or'#text').dataholds the text of a text node, which have no children (hence the dotted lines in the figure above).
Thus the code below should display the string 'very':
var theDiv = document.getElementById('div01');alert(theDiv.childNodes[1].childNodes[1].firstChild.data);
In practice, it's rarely desirable to work through the tree in this way because the exact number of text nodes (and hence child nodes) depends on the way the document was laid out and on how the browser handles text and white space, so the shape of the tree is not readily predictable.
Better approaches are:
- To give elements unique id attributes and use
node.getElementById(id_value)to return the object withid=id_valuein the tree starting atnode.[13] - To use
node.getElementsByTagName(element_name)to return an array of all objects in the tree starting atnodewhosenodeNameis the stringelement_name.
Finding a node is usually a precursor to changing it (or more accurately one or more of its children). As with navigating the DOM, there are many methods and fields which can be used to make changes. The following are perhaps the most useful:
document.createElement(element_name)creates an element object whosenodeNameis given by the stringelement_name.document.createTextNode(text_string)creates a text node object whose content is given by the stringtext_string.node.appendChild(newNode)addsnewNodeasnode's last child.node.insertBefore(newNode,oldNode)insertsnewNodeimmediately beforeoldNode, both being children ofnode.node.replaceChild(newNode,oldNode)relacesnode's childoldNodebynewNode.node.removeChild(oldNode)removesnode's childoldNode.innerHTMLis a string field representing the HTML 'inside' the node; giving it a new value is an easy way of changing the text belonging to a node, avoiding the need to create text nodes and add/replace them as children.[14] (Likedocument.write, inserting text intoinnerHTMLmakes validation impossible. For this reason, some browsers do not allow it in pages served as XML or XHTML. Further, the corresponding nodes in the DOM are often created by a different thread, so may not be available immediately to JavaScript, causing subsequent manipulations of the DOM tree to fail.)node.setAttribute(attr_name, attr_value)adds an attribute tonodewhosenodeNameis given by the stringattr_nameand whosenodeValueis given by the stringattr_value.
Given the HTML discussed above, the code in the following rather artificial example repeatedly changes the bold word 'very' in the first paragraph to 'somewhat' and also replaces the second paragraph.
Example 15-1 (http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg15-1.html)
JavaScript:
var words = ['very', 'somewhat'];var iWord = 0;var theDiv;var firstB;var secondP;var newP;function init(){ // Now the document has loaded, set up the required objects.theDiv = document.getElementById('div01');firstB = theDiv.getElementsByTagName('b')[0];secondP = theDiv.getElementsByTagName('p')[1];newP = document.createElement('p');newP.innerHTML = 'One problem is varying browser support.';// Alternate the text of the paragraphs.setInterval('changeText()',1000);}function changeText(){ iWord++; if(iWord>1) iWord = 0;firstB.innerHTML = words[iWord];if (iWord==1) theDiv.replaceChild(newP,secondP)else theDiv.replaceChild(secondP,newP)}
HTML:
<body onload="init();"><div id="div01"><p>Manipulating the JavaScript DOM is a <b>very</b> importanttechnique.</p><p>Varying browser support is the main problem.</p></div></body>
The ability to create new HTML elements and attributes enables us to re-write Example 14-2. In the original version, the div elements which became boxes are explicitly coded in the HTML, with an initial style which makes them invisible. Then when each rectangle object is created, the style of the corresponding div is changed. This approach requires the number of boxes which will be created to be known in advance.
The alternative is to create the divs dynamically.
Compare the following version of makeRect with that given
above.
var theBody; // Must be initialized to the <body> element.. . . . . . . . . .function makeRect(left,top,width,height){ var r = new Object();r.left = left;r.top = top;r.right = left+width;r.bottom = top+height;r.domObj = document.createElement('div');r.domObj.setAttributeNode('class','box');theBody.appendChild(r.domObj);r.domObj.style.cssText += 'left:'+left+'px; top:'+top+'px; width:'+width+'px; height:'+height+'px; border: solid 2px black;';r.clicked = false;r.domObj.onclick = function (){ if (!r.clicked) r.domObj.style.borderColor = 'yellow';else r.domObj.style.borderColor = 'black';r.clicked = !r.clicked;};return r;}
Note the following points:
- The global variable
theBodymust be initialized to thebodyelement object, e.g. bytheBody = document.getElementsByTagName('body')[0];. - The set of statements in bold then create a
div of the form
<div class="box" />. - We can bypass the need to create an
onclickattribute for the div by directly setting theonclickmethod in the corresponding object. The DOM object now doesn't need to link to the rectangle object; clicking on the div will automatically call the right event handler.
The complete HTML page is at
http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg15-2.html.
For a larger example of what can be done with JavaScript and dynamic changes to
both CSS and the HTML DOM, see
http://www.cs.bham.ac.uk/~pxc/infoweb/FifteenPuzzle.html.
16 Loading and processing XML
A set of techniques involving 'asynchronous JavaScript technology and XML' (AJAX) has become popular since about 2005. The following diagram shows the basic four steps involved.[15]

The numbers in the diagram correspond to the steps:
- The action is initiated by the web page or by a user generated event.
- An XMLHttpRequest object is invoked. It is given a URL and a 'callback' function. Browsers provide this facility in a number of different ways, but the core functionality is the same. The URL is used to generate an HTTP request, addressed to the same web server which supplied the HTML page. The request is asynchronous: once initiated it runs independently. Thus while waiting for the XML document to load, the browser can continue to respond to other user actions, e.g. scrolling, or to execute other JavaScript.
- Based on the URL in the request, the web server returns an XML document. The simplest kind of URL would give the location of an XML file. A more plausible URL would initiate a server-side program which generates XML, perhaps based on data acquired from a database.
- When the XMLHttpRequest object has correctly received all of the XML document and it has been converted to a DOM object, it executes the callback function it was given. This function typically takes the XML document object as its argument and uses it to update the HTML page by manipulating the HTML document object, as discussed in Section 15.
There are an important security restrictions imposed by browsers such as Firefox:
- An XMLHttpRequest object can only make requests via the
http:protocol. This ensures that web pages cannot read files on the local computer via thefile:protocol. - The requested file must be served from the same domain as the
web page, e.g. the web page reached via
http://www.myname.co.uk/...can only request XML files whose URL also begins withhttp://www.myname.co.uk/.
Some code needed to evoke an XMLHttpRequest object will be found in http://www.cs.bham.ac.uk/~pxc/infoweb/xhrlib.js. I have tried to write code which traps as many errors as possible -- it's not designed for 'real' use. It's not necessary to understand the details of the code in this module.
The key to using this library code is the function requestXML(url,
processor). This function:
- generates an XML HTTP request for the URL contained in the
string supplied as its first argument,
url - when the XML has been loaded, passes the XML DOM object
created by the browser to the function passed as its second argument,
processor.
The URL supplied must either be:
- relative to the web page (e.g.
../mydata.xml) - a full URL starting with
http://and the same domain name as the web page.
The function passed as the second argument must take one parameter. When the XML is loaded, this parameter will be set to the XML DOM object created by the browser. The function must then process the DOM object appropriately.
A simple example can be used to show how this works. We begin with the XML document stored at http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/data.xml:
<?xml version="1.0"?><!DOCTYPE data[<!ELEMENT data (person*)><!ELEMENT person (name, birthplace)><!ELEMENT name (#PCDATA)><!ELEMENT birthplace (#PCDATA)>]><data><person><name>Arjun Sen</name><birthplace>Birmingham</birthplace></person><person><name>John Smith</name><birthplace>London</birthplace></person><person><name>Mary Jones</name><birthplace>Cardiff</birthplace></person></data>
The task is to construct a simple HTML page which loads this file and displays names and birth places.
The body of the page just has a heading and an empty div element.
<body onload="init('display');"><h1>People and their Places of Birth</h1><div id="display"></div></body>
The init function below sets up a JavaScript variable
displayDiv to hold the DOM object corresponding to the empty
div element, and then initiates the asynchronous
request for an XML document, using the requestXML function from the
xhrlib.js file. Note that because a relative URL has been used in the call of
requestXML, it will be resolved to the same location as the web
page, and in particular the same domain. It is essential that the web page is
opened via the http: protocol and not the file:
protocol, i.e. the web page must be on a web server.
var displayDiv;function init(displayDivID){ displayDiv = document.getElementById(displayDivID);requestXML('data.xml', doDisplay);}
Because the request is asynchronous, init will terminate after
the call to requestXML; it will not wait for the file to be loaded.
When the file has been loaded, the JavaScript engine will call the function
doDisplay, with the XML document object as its argument.
The idea is to walk through the XML document object, creating a new HTML p element (i.e. a paragraph) for each person in the XML file. Each paragraph is then made a new child of the empty 'display' div.
The steps involved are:
- Use
getElementsByTagName('person')to get an array of the objects corresponding to the person elements in the XML document. - Iterate through the array extracting the text from the name and birthplace elements of each person element. Assume that the XML is valid, i.e. that each person element has only one name and one birthplace element and that the first child element of each is the text we want.
- Create a new paragraph whose inner HTML is the extracted text and add it to the end of the 'display' div.
This produces the following code.
function doDisplay(xmlDoc){ var persons = xmlDoc.getElementsByTagName('person');for (var iPerson = 0; iPerson < persons.length; iPerson ++){ var person = persons[iPerson];var txt = person.getElementsByTagName('name')[0].firstChild.data;txt += ', ';txt +=person.getElementsByTagName('birthplace')[0].firstChild.data;var newP = document.createElement('p');newP.innerHTML = txt;displayDiv.appendChild(newP);}}
The complete HTML page is at
http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg16-1.html.
An improved alternative in which the data is displayed in a table, constructed
without using the innerHTML field, will be found at
http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg16-2.html.
A more realistic example would display only part of the data. For example, if the user enters a name the page should respond by showing the person's place of birth. Extending Example 16-1 to achieve this is straightforward. The body of the page is defined as:
<body onload="init('display','name');"><h1>People and their Places of Birth</h1><p>Enter person's name:<input id="name" type="text" size="20" value=""/><input type="button" value="Show Place of Birth"onclick="showBirthplace();"/></p><div id="display"></div></body>
The user enters a name into the text input box and then presses the "Show Place of Birth" button. The initially empty "display" div element is then used to show the result.
The init function sets up the necessary objects and requests the
XML document. The callback function extracts and stores an array of the objects
corresponding to the person elements:
var displayDiv;var nameInput;var persons;function init(displayDivID,nameInputID){ displayDiv = document.getElementById(displayDivID);nameInput = document.getElementById(nameInputID);requestXML('http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/'+'data.xml', setupPersons);}function setupPersons(xmlDoc){ persons = xmlDoc.getElementsByTagName('person');}
Finally when the user presses the button, the array is searched for the
appropriate name and a result displayed. A minor issue is that, as noted in
Section 15, text values retrieved from an XML source may
contain extra white space. For this reason, xhrlib.js also contains
normalize_space(str) which normalizes a string by stripping any
opening and closing white space and replacing all other occurrences of white
space by a single space.[16]
function showBirthplace(){ var name = normalize_space(nameInput.value);if (name != ''){ var i = 0;var found = false;while (!found && i < persons.length){ var person = persons[i];found = normalize_space(person.getElementsByTagName('name')[0].firstChild.data) == name;i++;}if (!found){ displayDiv.innerHTML = 'Unknown person: '+name+'.';}else{ var birthplace = normalize_space(person.getElementsByTagName('birthplace')[0].firstChild.data);displayDiv.innerHTML = name+' was born in '+birthplace+'.';}}}
The complete HTML page is at http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg16-3.html.
In spite of the fact that it only displays part of the data, Example 16-3 still downloads the entire XML file. This is clearly undesirable:
- The file may potentially be very large and so its loading will cause an unacceptable delay, especially with a slow connection.
- Since the XML file must be available by HTTP, there is no way of hiding data which the user should not be able to access -- by inspecting the source code of the JavaScript, the user can find the URL of the XML file, and then download it directly.
The solution is server-side processing. For example, the URL used to access the required XML could be directed at a web page containing server-side script, written in a language such as PHP. The web server executes such script before sending the result to the client. For example, given a person's name, the server could send back some XML describing only that person. (The original data does not have to be stored as XML, since the program or script running on the server can obtain data from any source and then 'wrap' it in XML.)
When http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/data.php?name=John Smith is accessed,[17] it returns XML equivalent to:
<person><name>John Smith</name><birthplace>London</birthplace></person>
Given a name other than "John Smith", "Arjun Sen" or "Mary Jones", it returns the name it was given with birthplace as "unknown".
The JavaScript below demonstrates how an HTML page might interact with this PHP page. Notice that the HTTP request now occurs only when specific data is required.
var displayDiv;var nameInput;function init(displayDivID,nameInputID){ nameInput = document.getElementById(nameInputID);displayDiv = document.getElementById(displayDivID);}var name;function showBirthplace(){ name = normalize_space(nameInput.value);requestXML('http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/'+'data.php?name='+name, displayBirthplace);}function displayBirthplace(xmlDoc){ var birthplace = normalize_space(xmlDoc.getElementsByTagName('birthplace')[0].firstChild.data);if (birthplace == 'unknown')displayDiv.innerHTML = 'The birthplace of '+name+'is unknown.';elsedisplayDiv.innerHTML = name+' was born in '+birthplace+'.';}
The complete HTML page is at http://www.cs.bham.ac.uk/~pxc/infoweb/2007/Egs/Eg16-4.html.
(Server-side processing with Java will be covered in the Software System Components modules in Year 2.)
AJAX has significant advantages over other techniques for dynamically displaying data:
- It increases the separation between content and presentation. Content is transferred between the server and browser as XML. The web page only deals with presentation and user interaction.
- Changes to the page are 'smooth', at least compared to the alternative approach in which changes to content are handled by loading a different web page.
- It utilizes the advantages of XML as a medium for data transfer (e.g. high compatibility across platforms through its well-standardized text-based format; increased security compared to binary data; ease of processing through the XML DOM which closely parallels the HTML DOM).
References/Bibliography
See the other handouts for this module, which are available online.
Footnotes
[1] For a more detailed explanation, see the section on validity in my Summary of Core XHTML.
[2] HTML and JavaScript allow matched single and double-quotes to be used interchangeably. I generally use double-quotes for attribute values and single quotes for JavaScript strings as here. Use of either this or the opposite convention avoids un-necessary escaping.
[3] When talking about languages such as Java or C, it is conventional to put empty parentheses after the name of a method or function to distinguish it from the name of a variable or field. However, in JavaScript, as will be explained later, function values can be assigned to variables, so that it is important to distinguish the name of function from a use or call of the function. Hence in this document the name of a function will always be written without parentheses.
[4] This approach is particularly likely to fail with older browsers.
[5] Until its release, it was apparently called LiveWire or LiveScript.
[6] JavaScript has untyped
variables in that their type is not fixed. However JavaScript values are typed;
the typeof operator can always be applied to determine the current
type of a variable and types and type conversions are important in understanding
how JavaScript works. It's thus slightly misleading when JavaScript is said to
be an untyped language.
[7] Since JavaScript uses only real arithmetic, complex calculations involving integers such as times may yield apparently incorrect results if care is not taken (e.g. by rounding result values).
[8] Strictly the attribute value
should begin with javascript:.
[9] Parentheses after
Object are optional.
[10] Information hiding can be achieved in JavaScript by using local variables to store data, an approach not covered in this module. See my web page on Information Hiding in JavaScript.
[11] Older versions of
Internet Explorer are notorious for their poor support of the CSS
position property.
[12] Browsers may also split long blocks of text among adjacent text nodes, although sibling text nodes ought to be merged together according to the W3C standard.
[13] This seems to be more
browser compatible if node is document.
[14] However, setting
innerHTML does not work for XHTML served as XML on some browsers,
e.g. Safari 2.0.
[15] Redrawn from http://java.sun.com/developer/technicalArticles/J2EE/AJAX/IntroAjaxPageAuthors.html.
[16] Using the
prototype field of the String object, xhrlib.js also
attaches normalize_space to all strings, so that
normalize_space(str) and str.normalize_space() are
then equivalent.
[17] Strictly the space in the URL should be escaped as %20.