In this chapter, you'll look at some of the more complex capabilities of JavaScript. You learned the basics of using frames in Chapter 9, "Using Frames, Cookies, and Other Advanced Features;" you'll look at some more sophisticated examples here. You'll also explore techniques for storing data and learn about the data tainting feature, which enables you to overcome some of Netscape's security restrictions.
As you learned in Chapter 9 you can use nested framesets to create a complex framed document. When you combine nested framesets with JavaScript, you have to be careful about how you refer to fields in other frames.
For this section, let's return to the Fictional Software home page created in Chapter 9 turning it into a complex site using nested framesets. You'll find it looks much more like typical sites on the Web.
Let's start by creating the frameset document, shown in Listing 10.1.
Listing 10.1. (NESTED.htm) The frameset document for the nested framesets example.
<HTML> <FRAMESET ROWS="30%,*,52"> <FRAME NAME="contents" SRC="index.htm"> <FRAME NAME="main" SRC="fscmain2.htm"> <FRAMESET COLS="300,*"> <FRAME NAME="imagemap" SRC="map.htm"> <FRAME NAME="description" SRC="descrip.htm"> </FRAMESET> </FRAMESET> </HTML>
This listing uses the following frames:
Listing 10.2 shows the document for the second frame, which is adapted from the version used in Chapter 9.
Listing 10.2. (FSCMAIN2.htm) The second frame for the nested frames example.
<HTML> <HEAD> <TITLE>Fictional Software Company</TITLE> </HEAD> <BODY> Welcome to our web page! Fictional Software Company specializes in creating innovative, user-friendly software applications with descriptions filled with industry buzzwords. <P> We have a wide range of products (3 of them) to meet the needs of you and your company. Follow the links below for more information. <P> <UL> <LI><A HREF="spread.htm" onMouseOver="parent.description.document.form1.text1.value='Information about the spreadsheet';return true;"> Fictional Spreadsheet 7.0</A> <LI><A HREF="word.htm" onMouseOver="parent.description.document.form1.text1.value='Information about the word processor';return true;"> Fictional Word Processor 6.0</A> <LI><A HREF="data.htm" onMouseOver="parent.description.document.form1.text1.value='Information about the database';return true;"> Fictional Database 7.0</A> </UL> <HR> <I>(c)1998 FSC - designed by the FSC staff</I> </BODY> </HTML>
The third frame is an image map, which can be used for navigation. It uses event handlers to place informative messages in the text field in the fourth frame. Listing 10.3 shows the image map document.
Listing 10.3. (MAP.htm) The image map document for the nested frames example.
<HTML> <BODY> <MAP NAME="map1"> <AREA SHAPE=RECT COORDS="6,7,61,43" HREF=support.htm onMouseOver="parent.description.document.form1.text1.value='Support for our products';return true;"> <AREA SHAPE=RECT COORDS="73,8,129,42" HREF=compinfo.htm onMouseOver="parent.description.document.form1.text1.value='About our Company';return true;"> <AREA SHAPE=RECT COORDS="140,7,200,42" HREF=order.htm onMouseOver="parent.description.document.form1.text1.value='Order Products';return true;"> <AREA SHAPE=RECT COORDS="211,6,276,43" HREF=customer.htm onMouseOver="parent.description.document.form1.text1.value='Customer Service';return true;"> <AREA SHAPE=default HREF=fscmain2.htm> </MAP> <IMG SRC="fscmap.gif" USEMAP="#map1"> </BODY> </HTML>
Finally, the fourth frame contains a simple text field, which is used in place of the status bar to display descriptions as the user moves over links. Listing 10.4 shows this document.
Listing 10.4. (DESCRIP.htm) The description text field document for the nested frames example.
<HTML> <BODY> <FORM NAME="form1"> <INPUT TYPE="TEXT" NAME="text1" SIZE="40" VALUE="Look Here for help."> </BODY> </HTML>
This is a good example of how complicated frames programming can get. Both the middle frame and the image map frame address the text field in the fourth frame to display descriptions. The final document, as displayed in Netscape, is shown in Figure 10.1.
Figure 10.1 : The nested frames example, as displayed by Netscape.
One of the most talked-about issues on the Web today is the integration of databases with Web content. JavaScript isn't a very powerful language for database applications, mainly because it's limited to client-side use. However, there are some tricks you can use to store data in a database-like format in JavaScript. You'll look at several such techniques in the following sections.
The first technique may seem simple, but it's often overlooked. Because a JavaScript variable can hold any type of data, it can be used as a string array. For example, the following code creates a three-element string array and assigns values to its elements:
strings = new Array(3); strings[0] = "This is the first element."; strings[1] = "This is the second element."; strings[2] = "This is the third element.";
Each element of the array can be used as an ordinary string object. For example, this statement displays a substring from the third element of the array defined previously:
document.write(strings[2].substring(5,10));
If you've worked with Perl, a popular language for CGI programs, you've probably used associative arrays. An associative array is an array with names instead of indexes for each of its elements.
JavaScript doesn't officially include associative arrays, but you can easily simulate them using an object. The following statements define an object and assign values to three elements:
animals = new Object(); animals["frog"] = 2; animals["bear"] = 3; animals["chicken"] = 4;
This technique is useful if you are storing a number of named values, especially if the names are created during the course of your program.
One thing missing from JavaScript is two- or three-dimensional
arrays. However, you can easily simulate them by creating an array
of arrays. You can expand this technique to store any number of
items-either numbered or named-in an array. This makes it easy
to store just about anything.
Tip |
The solitaire game in Chapter 15, "Real-Life Examples III," uses several of these techniques to store information about the cards in use. |
In the first versions of JavaScript (Netscape 2.0 and 2.01), properties of a document were always available to JavaScript code in other documents. For example, if you loaded a page in one frame, a JavaScript program in another frame could access properties of the page-its links, anchors, and form elements.
Although reading properties of a document doesn't sound like much of a security risk, some clever folks found ways to exploit it. If there's a security risk of any kind on a Web page, you can be sure someone out there will find it and take advantage. Here are a few of the tricks that were possible due to this feature:
Although minor, these were risks to security and privacy. The public tends to worry about such things, particularly on the Internet (and Microsoft was encouraging them to). To deter these rumors and problems, Netscape quickly released a fixed version, Netscape 2.02.
The fix in Netscape 2.02 was to prevent a document from accessing properties of another document, unless it came from the same server. Thus, if your document loaded Netscape's home page in a frame, it couldn't access the links, anchors, or even the address of the Netscape page.
Although the fixed version prevented these problems, it also removed a useful feature. If you could access properties of a document in another frame, for example, you could create a "link summary" frame with a quick reference to all the links on the page.
Luckily, Netscape found a solution to make everyone happy, beginning with Navigator 3.0b5, which introduced data tainting. This enables you to access properties of a document in another frame, but not without evidence.
As an analogy, consider the security devices used in a modern record or video store. The simple solution to prevent theft is to keep all the items in locked cabinets, but that would prevent customers from browsing them. Instead, magnetic strips are attached to each item, and can't be removed. Thus, you can take an item off the shelf and look it over-but come near the exit, and alarms go off all over the place.
Data tainting does the same thing for data from other servers. Data from another server is marked, or tainted. The data is still useful, but it is marked. No matter what you do with the data-assign it to variables, use it in calculations, and so on-it remains tainted.
When a JavaScript program attempts to send data to a server-either
by submitting form data or by using an URL-it is checked for tainting.
If any tainted data is present, the user is alerted and allowed
to cancel the operation.
Note |
To send data to a server using an URL, the application could use the data as a document name or as a parameter. In either case, a CGI script could receive the data on the server. |
The actual tainting is done by using a special taint code
in storing the value. The taint code is unique for each server.
Thus, you can freely send data to the same server it was originally
taken from, but the user is warned if you attempt to send it to
a different server.
Note |
When data tainting is enabled, you can also access the value of password objects in a form. Because their value is tainted, though, you can't send this information to a server. |
To use the data tainting feature, you need to enable it using an environmental variable. The following command can be used to set the variable to enable tainting:
SET NS_ENABLE_TAINT=1
If you are using Windows 3.1, you can exit to DOS, type this command, then return to Windows. In Windows 95, the easiest method is to add the command to your C:\AUTOEXEC.BAT file, then reboot the computer. For Macintosh systems, you need to create a resource called NS_ENABLE_TAINT.
You can check whether the user has enabled data tainting with the navigator.taintEnabled ( ) method.
You can exercise some control over data tainting with two JavaScript functions to convert between tainted and nontainted values:
These functions return a tainted or untainted result, but do not
modify the original value. The main use for untaint
is to make values available to other scripts without security
restrictions.
Note |
Although you can add taint to any value with taint(), you can only untaint values that have the current program's taint code. There is no way to remove taint from a value that originates from another window. |
As an example of a multiserver application that takes advantage of data tainting, let's create a framed document that displays a link summary for an existing document-at any URL. The simplest part of this task is the frameset document, shown in Listing 10.5.
Listing 10.5. (MULTSERV.htm) The frameset document for the link summary application.
<HTML> <FRAMESET COLS="20%,80%"> <FRAME name="summary" SRC="linksumm.htm"> <FRAME name="destination" SRC="doc1.htm"> </FRAMESET> </HTML>
Next, let's create the link summary document for the first frame, which will use JavaScript to display a summary of the document in the second frame. This document is shown in Listing 10.6.
Listing 10.6. (LINKSUMM.htm) The main HTML document for the link summary JavaScript application.
<HTML> <HEAD> <TITLE>Link Summary</TITLE> <SCRIPT LANGUAGE="JavaScript"> function newloc() { // send other frame to new URL parent.frames[1].location.href = document.form1.text1.value; // update link summary self.location.reload(); } </SCRIPT> </HEAD> <BODY> </BODY> <H3>Link Summary</H3> <HR> <FORM NAME="form1"> <INPUT TYPE="text" NAME="text1" VALUE="enter new URL"> <INPUT TYPE="button" VALUE="GO" onClick="newloc();"> </FORM> <SCRIPT LANGUAGE="JavaScript"> // list links in other frame len = parent.frames[1].document.links.length; document.write("<B>Total links: " + len + "</B>"); // begin numbered list document.write("<BR>\n<OL>"); // reproduce each link here for (i=0; i < len; i++) { document.write("<LI><A HREF='"); document.write(parent.frames[1].document.links[i].href); document.write("'>"); document.write(parent.frames[1].document.links[i].pathname); document.write("</A>\n"); } document.write("</OL>"); </SCRIPT> <HR> </HTML>
This is where the real action happens. The JavaScript functions in this document create a summary of the links in the second document. The links are listed in a numbered list, with each linked to its corresponding document name.
Of course, when you first load the document, there will be no document in the second frame. You can use the text field and form in the link summary frame to load a new document, and the link information will be displayed.
Thanks to data tainting, this should work with any Web document. It will not currently work with framed documents, because it hasn't provided for multiple frames. The output of this program, as displayed by Netscape, is shown in Figure 10.2. In the figure, I've loaded Netscape's page, and the links on the page are listed.
Figure 10.2 : The output of the link summary, application.
Often, your program will need to maintain state information; you may display a series of pages and need to remember something between pages. Quizzes, questionnaires, and games often need to maintain state. There are two ways to do this:
Both of these have their advantages and disadvantages. If your application also uses CGI, for example, you may find cookies more useful. The next task is an example of using a frame to maintain state information.
As a complex example of using frames to keep track of state between pages, let's create a questionnaire. This program asks several questions; after you answer each question, it stores the answer in an array. The array is part of the script in the top frame; the bottom frame is used to show the questions. The frameset document for this example is shown in Listing 10.7.
Listing 10.7. (QUIZ.htm) The frameset document for the questionnaire.
<HTML> <FRAMESET ROWS="15%,*" onLoad="setTimeout('parent.MainFrame.NextQuestion();',1000);"> <FRAME NAME="MainFrame" SRC="quizmain.htm"> <FRAME NAME="QuizFrame" SRC="doc1.htm"> </FRAMESET>
Listing 10.8 shows the main program for the questionnaire. When you load the page, the questions are asked one at a time. After the last question, a summary of your answers is displayed. The final output of this program is shown in Figure 10.3.
Figure 10.3 : The questionnaire is complete, and the results are displayed.
Listing 10.8. (QUIZMAIN.htm) The main JavaScript program for the quiz example.
<HTML> <HEAD><TITLE>Questionnaire Example</TITLE> <SCRIPT LANGUAGE="JavaScript"> // global variables var answers = new Array(5); var questions = new Array(5); questions[0] = "What is your name"; questions[1] = "What is your age"; questions[2] = "What is your phone number"; questions[3] = "How many beans make 5"; var current = 0; var quest; // function to ask a question in other frame function NextQuestion() { if (current > 0) { ans = parent.QuizFrame.document.form1.question.value; answers[current-1] = ans; } if (current + 1 < questions.length) { text = questions[current]; parent.QuizFrame.document.open(); parent.QuizFrame.document.write("<HTML><BODY>\n"); parent.QuizFrame.document.write("<h1>" + "Question #" + current + "</ h1>"); parent.QuizFrame.document.write("<hr>"); parent.QuizFrame.document.write("<b>" + text + "?</b><br>"); parent.QuizFrame.document.write("<FORM NAME=\"form1\">\n"); parent.QuizFrame.document.write("<INPUT TYPE=\"text\" NAME=\"question\"> "); parent.QuizFrame.document.write("<BR><INPUT TYPE=\"BUTTON\" VALUE=\"Submit Answer\" "); parent.QuizFrame.document.write("onClick=\"parent.MainFrame.NextQuestion();\" >"); parent.QuizFrame.document.write("</BODY></HTML>"); parent.QuizFrame.document.close(); current++; } else { parent.QuizFrame.document.open(); parent.QuizFrame.document.write("<HTML><BODY>\n"); parent.QuizFrame.document.write("<h1>Your answers:</h1><hr>"); for (i=0; i<(questions.length-1); i++) { parent.QuizFrame.document.write("<B>" + questions[i] + "</B>: " + answers[i] + "<BR>"); } parent.QuizFrame.document.write("</BODY></HTML>"); parent.QuizFrame.document.close(); } } </SCRIPT> </HEAD> <BODY> <H1>Questionnaire</H1> </BODY> </HTML>
This program uses the questions array to store the questions and the answers array to store the user's answers. When the page is loaded, the NextQuestion() function is called to display a question.
Each question is displayed in the bottom frame. The document you create in this frame also has a JavaScript event handler, which calls the NextQuestion() function (in the top frame) when the question is entered.
In this chapter, you learned some of the more complex aspects of JavaScript and how it can work with complicated pages and data:
When you master the techniques in this chapter, you've come a long way toward becoming a JavaScript expert. Continue your studies with one of the following:
Using data tainting, I can access properties of another document, such as links and anchors. Is there any way to read the HTML source of the document itself? | |
Currently, there is no way to do this. The properties made available in the object hierarchy (explained in Chapter 5 "Accessing Window Elements as Objects") are all you can access via JavaScript. This is not expected to change. | |
Can a JavaScript program in one frame read variables (not properties) defined by a JavaScript program in another frame? | |
Yes. Just treat the variable name as a child of the frame's window object. For example, parent.frame1.score refers to the score variable in the frame1 frame. | |
What happens if the document I load into a frame is a framed document itself? Will this cause an error? | |
This will not cause an error-in fact, it works fine. The frames of the document are created within the frame in which it is loaded. Functionally, this is treated the same as a nested frameset. |