Click on the image to change it.
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)
Click on the image to change it.
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 . 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 image function step(imgNo) { i++ if(i>1) i=0 document.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=0 document.getElementById(imgID).src = pic[i].src } XHTML change:
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 time var dayInMonth = now.getDate(); // Day of the month, from 1 to 31 var 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 number var hr = now.getHours(); // Hour, from 0 to 23 var min = now.getMinutes(); // Minute, from 0 to 59 var sec = now.getSeconds(); // Second, from 0 to 59 var 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
=
The box below should change colour regularly from black to red and back again.
Some notes on this example: The style attached to all elements of class box causes 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 init sets the value of the global variable box1 to the JavaScript object representing the div. The setInterval function is then used for animation (see the discussion below); changeColour will be called once every second. The function changeColour then sets and re-sets the border-color style 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 of time_in_msec milliseconds. It returns a timer identifier which should be saved if it will be necessary to stop the activity. clearInterval(timerID) stops the action created by setInterval: timer1 = setInterval('run()', 2000); . . . . . . clearInterval(timer1); setTimeout(JavaScript_string, time_in_msec) executes the JavaScript given in the first string argument after one interval of time_in_msec milliseconds. (If the action has not taken place, it can be stopped via clearTimeout(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: 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: 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: 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:Manipulating the JavaScript DOM is a very important technique.
Varying browser support is the main problem.
Manipulating the JavaScript DOM is a very important technique.
Varying browser support is the main problem.
Enter person's name:
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: