Notice: This material is excerpted from Special Edition Using JavaScript, ISBN: 0-7897-0789-6. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.
One of the tremendous advantages of hypertext is its ability to link together many pieces of information in a nonlinear fashion. Rather than having to read a book one page at a time, you can leap from one link to the next as you explore different topics and their interconnections. However, sometimes this easy access to information can have unfortunate and cumbersome side effects.
One such side effect occurs when you find yourself moving back and forth between a small set of pages, over and over again. This happens because of a problem with presentation: the information you want is scattered over more than one page. If you are reading a tutorial, for example, you may find that you are revisiting the Table of Contents page with dreary repetitiveness. You can avoid this by having multiple browser windows open at the same time, but this often takes up too much of your display.
This chapter focuses on the emerging HTML frames technology, which addresses this issue of presentation. Both the Netscape Navigator browser and Microsoft's Internet Explorer version 3.0 enable you to split a single browser window into multiple, independent subwindows, each containing its own URL. These subwindows are known as frames. We will explore frames technology and its close relationship to JavaScript in some detail.
To do this we must first address the related topic of data storage. Sophisticated applications often require both data presentation and data storage/management capabilities. Unfortunately, information or parameter storage is particularly difficult. In Netscape data may be temporarily saved between reloads using the location.search property, and also on the command line. In addition, both Netscape and Microsoft provide a persistent means of storage via cookies. We explore both methods in the first section of this chapter, and then subsequently illustrate how such data can then be used to format a page on the fly.
Because Netscape is extremely security-conscious, they have made it difficult to store or load data-even between document reloads. Chapter 8 briefly described one method of storing simple data between document reloads via a string saved in location.search. This special location.search property is often referred to as the command line. A script can retrieve this string and redraw itself based on information in the string. We first examine this approach to data storage in more detail. This is followed by a discussion of the more permanent storage option offered by cookies.
The "JavaScript Object Hierarchy" section of chapter 8 discusses the location object and its various properties, including the search property.
If you have a lot of data, you might appreciate having it accumulated and stored for you. You can use a submit widget or form.submit() to have the browser collect all of your data, encode it, and store it in the command line. As we will see below, the output is very hard to read and looks like scrambled text.
Another possibility is to store data in dynamic arrays in a window or, better, in the frameset document. Unfortunately, this too is unstable. If you have database like data, it must all be hand coded into the document that will host it, although you could use an HTML builder or other programming tool to automatically create the HTML document.
The only possibility that offers any permanence is cookies. Cookies are lines in a file that the browser enables you to write to disk. This file will be named cookies.txt on a Windows machine, or just cookies on a UNIX or Macintosh. Under Netscape Navigator this file will be in your Netscape directory or folder. Cookies are limited in size and number. Nevertheless, cookies are extremely useful. They are frequently used in the same way as a Windows .ini file, a Macintosh Preferences file, or a UNIX.rc file. The next two subsections examine both the command-line approach and the cookie approach.
Using the submit() routine to store form field values in the command line leads to a location that looks like listing 19.1.
Listing 19.1 A Typical Value for the location.search Property ?myname=Mona+M.+Everett%2C+Ph.D.&tc=navy&lc=blue&vc=orange &aname=Mona+M.+Everett%2C+Ph.D.&myimage=DBLACE4.jpg&mytext =navy&mylink=blue&myvlink=orange&myurl=http%3A%2F%2Fwww2.b est.com%2F%7Edsiegel%2Ftips%2Ftips_home.html%22&cmmt=Welco me+to+my+little+home+page+builder.++There%27s+no+tellin%27 +just+how+much+this+page+can+be+expanded.++What+do+you+thi nk%3F%0D%0A&subbtn=Submit
If you examine this output, you may be able to discern some patterns. First of all, it begins with the characteristic question mark (?), which delineates the location.search string. Second, most of the information seems to occur in pairs with the field name as the left-hand member and the field value as the right-hand member. The pairs are separated by the character ampersand (&).
There are absolutely no spaces in this output. Every space has been replaced by a plus sign (+). Various escape sequences containing the percent sign (%) occur in this string. These sequences are used to encode non-alphanumeric characters in the input. In particular, any punctuation has been replaced by such a sequence.
The section on "Global and Local Variables" in chapter 9 discusses character escape sequences in more detail.
JavaScript does not give you a lot of tools with which to dissect this sequence. Let's see how we can use the ones we have. First, there are a pair of built-in functions to handle the escape sequences. These are escape(), which takes a non-alphanumeric character and hands you back the coded sequence, and unescape(), which reverses the process. Second, we can use the substring method of the String object to "walk" through a string. A statement of the form
myString.substring(start,stop)
extracts all the characters starting from position start and ending at the last character just before the position stop. This enables us to examine each character in the string, one at a time, if we want. Based on the character encountered, we can replace it or take some action.
The Listing 19.2 has two functions that use this approach. The first one, which decodes the command-line search string, provides the core of the page rewrite code. The second changes all of the < and > to < and >, respectively, so that you can write out HTML to the page. This function is not actively called in the page, but is used for debugging using document.write(). The latter function is also really useful if you want to write HTML dynamically to your page to show your user how to do something. Let's examine the function that decodes the command-line search string, arraySubParms(),shown in listing 19.2. Note that this function presupposed that an array named parms has been declared as a global variable, and also initialized.
Listing 19.2 A Function That Dissects the Command Line into its Component Parts function arraySubParms(astr) { k = astr.length astr = astr.substring(1,k) bstr = '' counter = 1 for (i = 0 ; i <= k ; i++) { ccStr ='' ccStr = astr.substring(i,i+1) if (ccStr == '+') ccStr = ' ' if (ccStr == '%') { var xx = astr.substring(i,i+3) ccStr = unescape(xx) i += 3 } // car if (ccStr == '=') { parms[counter] = bstr bstr = '' continue } //right-hand member of pair if (ccStr == '&') { parms[parms[counter]] = bstr counter++ bstr = '' ccStr='' continue } bstr += ccStr } }
When arraySubParms(astr) receives a string, it immediately finds it length and uses that value to chop off the first character, which is the question mark (?), that starts the location.search string. It then begins a loop that cycles through every character in the string with the statement ccstr = astr.substring(i,i+1). The variable ccstr is then checked to see if it is equal to the plus sign (+). If it is, then that character is replaced with a space. If ccstr is a percent sign, the function uses the substring function again to grab three characters, starting from its current position. It then uses the built-in function, unescape, to turn these three characters back into an ASCII character.
Because three characters instead of one are used up, the pointer into the string, i, must be advanced by three with the statement i+=3. In either of these two cases (+ or &), the next two conditional tests will fail, and ccstr, which may have been modified, is added to bstr.
The browser places all of the form element names and their values into the command line as name=value pairs. Each pair is terminated by an ampersand (&). The next two conditional tests extract the left and right members of the pair and place them into the left and right members of the associative array parms. If the function finds that equal sign (=), it knows that bstr-which has steadily been accumulating characters-now holds the name of the element.
Another variable, counter, is used to keep track of the current index into the parms array. The left member is set with the statement, parms[counter] = bstr. The variable, bstr, is set to the empty string at this point so that it can start accumulating characters anew. The value of counter is not advanced. A continue statement is used to bypass the rest of the loop so that the equal sign (=), which was just seen, is not added into the new value of bstr.
JavaScript associative arrays are one dimensional arrays of pairs. The left and right members of a pair are set differently. Set the left member using a numerical index into the array, for example, myArray[n]=lvar. Set the right member with an index equal to the value that you placed into the left member, for example, myArray[myArray[n]] = rvar. You can also use myArray[lvar] = rvar if lvar has not changed between setting the left and right sides of the pair.
If the next test, for the ampersand (&), yields true, the function knows that it has now accumulated the right-hand member of the array in bstr. It sets the right-hand member with the statement, parms[parms[counter]] = bstr. Remember that you set the right-hand member with the index as the name of the left-hand member, not the index itself. The processing of the name=value pair is now complete. Again, bstr is set to the empty string in anticipation of the next loop iteration. In this particular case, however, counter is now incremented with the counter++ statement.
When the function finally reaches the end of the location.search string, you will then have all of the variables in the global parms array. You can now use them anywhere within the current window or in any window that you create.
The second noteworthy function from the encdec.htm file is the toprint() function, which changes all occurrences of the < and > characters in any string into their corresponding control codes. This seemingly trivial operation is, in fact, very important. This is because the < and > characters are interpreted by HTML. If you want to write HTML to your document, you must somehow prevent them from being interpreted as HTML delimiters. Converting them to control codes does the trick. This function is very useful for debugging or to show HTML example code on your pages.
We will conclude this discussion of command-line parameters by examining the remaining code from file encdec.htm. It uses the global parms array that you have just prepared to rewrite your page according your specifications. Listing 19.3 shows the page rewrite code itself.
Listing 19.3 Rewriting a Web Page using Command Line Data var astr = location.search if (astr != null && astr != ''){ // start conditional var parms = new createArray(12) arraySubParms(astr) astr = '<BODY BGCOLOR="linen" ' astr += 'TEXT="'+ parms['mytext'] + '" ' astr += 'LINK="'+ parms['mylink'] + '" ' astr += 'VLINK="'+ parms['myvlink'] + '" ' astr += 'ALINK="red" ' astr += 'BACKGROUND = "NewImages/' + parms['myimage'] + '" ' astr += '><BR>' document.write(astr) //document.write(toprint(astr)) } // end conditional else document.write('<BODY>') document.write('<TABLE ALIGN=RIGHT WIDTH=350 BORDER=1>') document.write('<TR><TD>') document.write('<FONT SIZE=7 COLOR= "indianred">') document.write('<CENTER>' +document.title + '</CENTER>') document.write('</TD></TR></TABLE>') document.write('<LEFT><B>') document.write('This page is an example of dynamically revised by ¬ a header script which acts on information stored in the ¬ command line. That information is based on user\' choices.') document.write('</B></LEFT>') document.write('<BR CLEAR ALL>') document.write('<HR>') var astr = location.search if (astr != null && astr != ''){ // start conditional astr ='<CENTER><FONT SIZE=7 COLOR="' + parms['link'] + '"><B> ' astr += parms['aname'] + '</B></FONT></CENTER>' document.write(astr) } // end conditional document.write('<HR><BR>')
Let us examine the operation of this script in some detail. There are several points worth noting. First of all, the location.search property is examined to make sure that it is not null or the empty string. If location.search does not contain a valid string, then most of the script processing is skipped. Two if...else statements are used for this purpose.
After the search string has been obtained and the parms array filled in by the call to arraySubParms(), the script starts building the <BODY> statement. Note that it builds it into a string and does not write it immediately with document.write. Note, too, the commented-out call to printit(), which was used during debugging to see if the string was built correctly.
Once the string has been assembled, a <BODY...> statement, which sets the background image and colors, is written to the document. If there was no search string, a plain <BODY> statement is written.
The script then writes a nice header for the document. The script next uses a second conditional clause to write your name in large letters. It had to check for the existence of a search string in order to do so. If the search string is present, you get your name; if it is absent, you get brief directions on using the page. When the header script is complete, the HTML on the page is interpreted by the browser.
Because all the form elements can be cleared with a submit, this program is polite and restores all of them from the global parms array. Instead of writing each one separately, it iterates through the form.elements array. Remember that the array was created with the element name as the left-hand member of the array. This makes it easy to get the correct variable in the form element. This could have also been done using numerical indexing. The routine shown is particularly useful if you have a large number of elements to restore.
Notice that a couple of form elements were included that were not used to construct the page. They were included here in order to provide a lot of escaped characters and a longer string of text with which to test the script.
The only method you can use to store variables between invocations of Netscape Navigator and Internet Explorer v3 is the cookie approach. This is also the only approach that works with windows in different hierarchies. Since the Internet Explorer release is still in beta test at the time of this writing we will focus on the version of cookies using by Netscape Navigator.
Cookies were originally designed to enable a server to save information on the client's disk. When the client contacted that same host at a later time, the previously saved cookie would be sent back to the server. Cookies are therefore useful if a browser connection is interrupted and you want to pick up where you left off. They are also useful in case the server crashes and later wants to pick up where it left off. Cookies are now available for general use in JavaScript.
Cookies have the following five parameters:
Only the first one, which is a familiar NAME=VALUE pair, is required. All of the others are optional. However, if you do not save an expiration date, the cookie automatically expires when you close Netscape-not something you want to happen if you want to keep information from session to session. The various parameters are separated by semicolons. If you create a cookie with the same name and path as a cookie already in existence, the new one overwrites the existing one.
Although servers can write named cookies one at a time, JavaScript cannot. You can set an individual named cookie with the statement, document.cookie='cookiename=xxxx', but when you retrieve document.cookie, you get a string consisting of all of the cookies. Currently, the only way to retrieve an individual cookie is too search through the entire set of cookies obtained from document.cookie. Consequently, it helps to add a prefix or suffix to the names of your cookies with little-used characters. This makes them easy to find with IndexOf().
Let's examine each of the cookie parameters in turn. As stated, the NAME=VALUE parameter is an associative pair. This means that it lends itself nicely to being stored in arrays and placed in form elements. This is the only required element of the cookie. The expires=DATE parameter is used to describe the expiration date for the cookie. As defined by Netscape, the date format must be "Wdy, DD-Mon-YY HH:MM:SS GMT" with the separators exactly as given. If you do not want persistent data between browser invocations, leave out this expiration date. If you want your cookie to never expire, give it a date several years in the future.
The path=PATH parameter is used to limit the search path of a server that can see your cookies. This is analogous to specifying a document BASE in an HTML document. If you use a slash (/), then everything in the domain can use your cookies.
The domain=DOMAIN_NAME parameter is only useful if the server is setting the cookie or, if for some reason, you want to generate a cookie that is available to the server. If the server generated the cookie, then the default domain is the domain name of the server that generated it. Finally, the parameter, secure, indicates that the cookie should only be sent if there is a secure client/server relationship.
Listing 19.4 shows the cookie versions of the routines for saving and restoring persistent information. In this case, the information goes to, and comes from, the document cookie rather than the command-line search string. These routines have been liberally modified from the original versions, which were written by Bill Dortch and placed in the public domain.
Listing 19.4 Saving and Restoring Document Cookie Information function fixSep(what) // escapes any semicolons you might have in your data { n=0 while ( n >= 0 ) { n = what.indexOf(';',n) if (n < 0) return what else { what = what.substring(0,n) + escape(';') ¬ + what.substring(n+1,what.length) n++ } } return what } function toCookie() { document.cookie = '' nform = document.data for (i=0 ; i<nform.length; i++) { expr =makeYearExpDate(1) astr = fixSep(nform.elements[i].value) //astr = nform.elements[i].value astr= nform.elements[i].name + '=' + astr + ';expires=' ¬ + expr + ';path=/' document.cookie=astr } } function makeYearExpDate(yr) { var expire = new Date (); expire.setTime (expire.getTime() + ((yr *365) *24 * 60 * 60 * 1000)); expire = expire.toGMTString() return expire } function getCookieAt(n) { e = document.cookie.indexOf (";", n); if (e == -1) e = document.cookie.length rstr= unescape(document.cookie.substring(n,e)) return rstr } function fromCookie() //restores summary fields from cookie { nform = document.data astr = document.cookie alert(astr) cl = astr.length counter=0 for (i = 0 ; i < nform.length ; i++) { nstr = nform.elements[i].name + '=' ll = nstr.length jx = 0; while (jx < cl) { k = jx + ll; xstr = astr.substring(jx,k); if (xstr == nstr) { nform.elements[i].value = getCookieAt(k); break ; } jx = document.cookie.indexOf(" ", jx) + 1; if (jx == 0) break ; } } } function arrayFromCookie() // fills global array from cookie { astr = document.cookie cl = astr.length k=0 jx = 0; for (i = 0 ; i < 6 ; i++) { jx=astr.indexOf(' ',jx) k = astr.indexOf('=',jx); xstr = astr.substring(jx+1,k); parms[i]=xstr; parms[parms[i]] = getCookieAt(k+1); jx = astr.indexOf(";", jx) + 1; if (jx <= 0 || i > 10) break ; }
The function makeYearExpDate() enables you to set the expiration date for several years in the future. It was designed for really persistent cookies. If you want a shorter time, you can easily modify this routine. Note that this function uses the Date object heavily. The static method, Date.getTime(), returns a neatly formatted date string, while the method, Date.toGMTime(), returns the date converted to Greenwich mean time, which is what the cookie expiration mechanism expects your cookies to contain.
The function fixSep() escapes any semicolons that your variables might have. It is highly undesirable to store semicolons in the cookie parameters because the semicolon is the parameter separator. You could, in fact, escape all the non-alphanumeric characters in the entire string. However, this would make it difficult to read, especially if you simply want to look at the cookie.
The function, GetCookieAt(n), retrieves the cookie value starting at an offset of n characters into the cookie string. It replaces all escape sequences with their ASCII values. The function, FromCookie(), restores all of the summary forms variables from the cookie. It is really an undo function.
The final function, arrayFromCookie(), is called by the page rebuilding routines to build the global array, parms, from which the page is rewritten. Notice that we did not have to change the page rebuilding code from that of listing 19.3. We only changed the routine to build the parms array. Notice also that we can retrieve the value of a single cookie entry by indexing into the parms array.
Frames are one of the most important new features to be added to HTML. Frames allow multiple subwindows-or panes-in a single Web page. This gives you the opportunity to display several URLs at the same time, on the same Web page. It also allows you to keep part of the screen constant while other parts are updated. This is ideal for many Web applications that span multiple pages, but also have a constant portion (such as a table of contents). Before you learn about the implications of frames on JavaScript, a very brief tutorial on frames will be presented.
Frames in HTML are organized into sets which are known, appropriately enough, as framesets. In order to define a set of frames one must first allocate screen real estate to this frameset, and then place each of the individual frames within it. We will examine the syntax for the HTML FRAMESET and FRAME directives in order to understand how frames and framesets are organized.
One of the most important, and most confusing, aspects of frames is the parent/child relationships of frames, framesets, and the windows that contain them. The first frameset placed in a window has that window as its parent. A frameset also can host another frameset, in which case the initial frameset is the parent. Note that a top level frameset itself is not named, but a frameset's child frames can be named. Frames can be referred to by name or as an index of the frames array. Figure 19.1 shows the overall hierarchy of frames, framesets, documents, and windows in Netscape Navigator.
FIG. 19.1 The hierarchy of windows, documents, and frames employs a complex, but consistent set of referencing rules
You can divide your window real estate with a statement of the form <frameset cols=40%,*>. This frameset statement divides the window horizontally into two frames. It tells the browser to give 40 percent of the window width to the left-hand frame, frames[0], and anything remaining to the right-hand frame, frames[1]. You can explicitly give percentages or pixel widths for all frames, but it is more useful to use asterisk (*), for at least one parameter. Use the wildcard character (*) for the widest frame, or for the frame that is least likely to be resized. This helps ensure that the entire frameset is displayed on a single screen. You can also divide the window vertically with a statement like <frameset rows=20%,*,10%>. This statement gives 20 percent of the available window height to the top frame, frames[0], 10 percent to the bottom frame, frames[2], and anything left to the middle frame, frames[1].
You cannot divide a window both horizontally and vertically with one frameset. To do that, you must use nested framesets.
The subsequent <FRAME...> statements define the name, source (URL), and attributes of each frame in the frameset. For example,
<FRAME SRC='menu.htm' NAME='menuframe' MARGINWIDTH=2 MARGINHEIGHT=2 SCROLLING=YES>
defines a frame into which the menu.htm file will be loaded. This frame is named menuframe
Unless you are designing a ledge (a frame that never changes) and you know it will always be displayed in the frame, make the frame scrollable. You can enter an explicit SCROLLING attribute, which should be the value YES or NO, but the frame will default to SCROLLING=YES. Scrolling is much kinder to your users. You might have a very high resolution display, but a lot of computers, particularly laptops, do not. The MARGINWIDTH=xx and MARGINHEIGHT=xx attributes also allow you some latitude in how you present your document within a frame.
Many browsers do not yet understand frames. Ideally, you should provide a version of your document that does not use frames for such browsers. At a minimum, you should warn the users about the presence of frames in your document using a <NOFRAMES>...</NOFRAMES> clause
Make sure you have an initial URL to load into the frame, even if that URL is just a stub. Otherwise, you might find that the browser has loaded an index to the current directory. If you want to use a frame in the frameset to load other documents from a link, you must specify the target frame like this:
<A HREF='netcom.com/home' TARGET='menuframe'>Netscape</A>
Frames are a sophisticated way to build Web pages; you can keep you menu in one frame and display your content in another. However, it is easy to go overboard and have too many frames. If you present too much information in several different small frames, the user will probably be scrolling quite often. Since the whole purpose of frames is to present information in a pleasing manner, it is important not to try the user's patience. Frames can be a powerful tool, but they should be used judiciously.
Framesets are easy to build, although their hierarchy can become complex if they are nested. Listing 19.5 shows a simple frameset document. For it to display correctly, there must be HTML documents with the names given by the SRC attribute in each FRAME definition. When this code is loaded into Netscape Navigator, the page shown in figure 19.2 appears.
Listing 19.5 A Simple Frameset <HTML> <HEAD> <TITLE><Simple Frame</TITLE> <SCRIPT></SCRIPT> </HEAD> <FRAMESET cols=40%,*> <FRAME SRC="menu_2.htm" NAME="menuFrm" SCROLLING=YES MARGINWIDTH=3 MARGINHEIGHT=3> <FRAME SRC="display.htm" NAME="displayFrm" SCROLLING=YES MARGINWIDTH=3 MARGINHEIGHT=3> <NOFRAMES> You must have a frames-capable browser to <A HREF="noframes.htm">view this document</A> correctly. </NOFRAMES> </FRAMESET> </HTML>
FIG. 19.2 Framesets contain multiple frames and reference multiple URLs.
When building a frameset, always remember the following rules:
One of the most difficult concepts about framesets and frames is how they are referenced. For the simple frameset previously shown, you can make a simple roadmap of the object references. When you want to reference the child frames from the frameset, you can use the following references:
When one of the frames references its parent frameset, this object reference is used:
The contents of each frame are referenced as properties of the frame. For example, the frameset can access the document object of menu_2.htm as frames[0].document or menuFrm.document
Frames can be nested in two ways. We will illustrate both types of nesting by putting another frameset inside the displayFrm frame object defined in listing 19.5. To understand the first method, call the original frameset Frameset A. The frameset declaration shown in listing 19.6 nests a second frameset, referred to as Frameset B, within Frameset A. It does this by replacing frames[1] (the displayFrm frame) with another frameset. The auxiliary files menu_3.htm, pics.htm, and text.htm are also required.
Listing 19.6 Example of Nested Frames in which a Frame is Replaced with Another Frameset <HTML> <HEAD> <SCRIPT> </SCRIPT> </HEAD> <frameset cols = 30%,*> <frame src = 'menu_3.htm' name='menuFrame' marginwidth=3 ¬ marginheight=3> <frameset rows=66%,*> <frame src='pics.htm' name='picFrame' scrolling=yes marginwidth=3 ¬ marginheight=3> <frame src='text.htm' name= 'textFrame' scrolling=yes ¬ marginwidth=3 marginheight=3> </frameset> <noframes> You must have a frames-capable browser to ¬ <a href=text.htm>view this document</a> correctly. </noframes> </frameset> </HTML>
Referencing in this type of nested frameset is no different than the type of object references described for a simple frameset. When a frameset references a child frame, the following object references are used:
When any of the component frames refers to the frameset that contains it, the following reference is used:
The second method uses URLs to achieve nested framesets. We will set Frameset B's displfrm to an URL that contains a framed document. This URL will come from the file displfrm.htm and will create the frames picFrame and textFrame. In this case, the object references are somewhat more complex. When the parent refers to its child frames it uses the following:
When the child frames refer to their frameset parent, these object references are used:
Specifying an empty URL in a frame declaration can cause the index file in the server's current directory to be loaded into the frame. Anyone can then open any of the documents listed in that index file. This can be considerably detrimental if you do not want to give users unrestricted read access to that particular directory
At this point, you know how to refer to parent framesets in frames, and also know the correct object references for child frames of a frameset. The next topic explores interframe communication. This example uses the files c20-3.htm, menu_2.htm (shown in listing 19.7), and display.htm. Make sure you place all these files in the same directory, and then load c20-3.htm into your browser. The file, menu_3.htm, that's loaded into the left frame provides a simple and useful example of interframe communication.
The menu3.htm file contains some links to well known sites. A TARGET that points to the displayFrm frame is given for each link. If you click a link, the URL loads into the displayFrm frame instead of into the menuFrm frame. Note that you cannot refer to the "parent" object when you use a TARGET. To experiment with the page, click several links. After you have done this a few times, try to go backwards using Netscape's Back button.
Netscape's Forward and Back buttons work on the entire document-not on individual frames
This limitation can certainly make life difficult, especially if you follow several links in the displayFrm and now want to get back to an intermediate one. Fortunately, there is a way to do this, but you must specifically provide for it. Notice the two small image buttons below the links. If you click the left arrow, the displayFrm frame reverts to the previously visited URL. Similarly, the right arrow takes you forward in the frame.
Listing 19.7 Using URLs to Create Nested Framesets <HTML> <HEAD> <TITLE>MENU.HTM</TITLE> <SCRIPT> function writetopic(what) { aWin = self.parent.displayFrm //aWin = self.parent.frames[1] aWin.document.close() aWin.document.open() aWin.document.write('<CENTER><H2><B>' + what + '</B></H2></CENTER>') aWin.document.close() } </SCRIPT> </HEAD> <BODY BGCOLOR='darkslateblue' TEXT='linen' LINK='corel' ¬ VLINK='darkcorel' ALINK='yellow' > <CENTER><FONT SIZE=7 COLOR="yellow"><B>MENU<B></FONT><CENTER> <H3><HR></H3> <FORM NAME="menuForm"> <INPUT TYPE='button' NAME="writeDisp" VALUE='Write to Display Frame' onClick='writetopic("Coming to you from ¬ <BIG><I>menuFrm</I></BIG>..." )'> </FORM> <H3><BR><HR><BR></H3> <H3><BR><HR SIZE=5 WIDTH=80%><BR></H3> </BODY> </HTML>
Another interesting aspect of frames is revealed if you attempt to use the View Source option of Netscape Navigator. Only the code for the frameset appears-the code for the frames contained in it does not. This is one approach to provide some simple protection for your source code. However, it only keeps novice users from seeing your code; experienced users can defeat this by loading the URLs referenced by the individual frames into a single browser window, and then using View, Source on that window.
The current release of Netscape Navigator does not reliably reload documents containing frames. This means that if you are editing a document and you press the Reload button, the most recent version of that document might not be reloaded
The examples in the files c20-4.htm and c20-5.htm do simple rewrites of documents into adjacent and foreign frames. We will now expand on that by taking the Text Object example from chapter 8 and writing it to a frame, rather than to a new window. The file c20-6.htm defines the frameset that loads the subsidiary documents setnote.htm and note.htm into its frames. The file setnote.htm contains a button that calls a writeNote() routine to write the new HTML into the frames[1] frame. The file note.htm is just a stub so you don't have to write an empty URL. Listing 19.8 shows the code for the writeNote() function. Figure 19.3 shows what happens when the note is written into the frame.
FIG. 19.3 Documents can be dynamically written into frames.
Listing 19.8 The Code for the writeNote() Function function writenote(topic) { topic = 'This is a little note about rewriting adjacent frames." topic += " You do it the same way as you would to rewrite" topic += " or originally write a window." aWin = self.parent.displayFrm ndoc= aWin.document ndoc.close() ndoc.open() astr ='<HTML><HEAD><BR><TITLE>' + topic + '</TITLE>' astr +='</HEAD>' astr +='<SCRIPT>' astr +='function closeNote(aName){' astr +='self.close()' astr +='}' astr +='function saveNote(aName){' astr +='}' astr +='<\/SCRIPT>' astr +='<BODY>' astr +='<FORM>' astr +='<TABLE ALIGN=LEFT BORDER><TR ALIGN=CENTER><TD>' astr +='<INPUT TYPE=button NAME=saveBtn VALUE="Save" ONCLICK="saveNote()" >' astr +='</TD>' astr +='<TD ROWSPAN=4>' + topic astr +='</TD>' astr +='</TR><TR ALIGN=CENTER><TD>' astr +='<INPUT TYPE=button NAME=closeBtn VALUE="Close" ONCLICK="closeNote()" >' astr +='</TD></TR>' astr +='<TR><TD><BR></TD></TR>' astr +='<TR><TD><BR></TD></TR>' astr +='</TABLE>' astr +='</FORM>' astr +='<BR CLEAR=ALL><H3><BR></H3>' astr +='Note: Save button is not active yet' astr +='</BODY></HTML>' ndoc.write(astr) ndoc.close() }
You have already learned that a frameset document cannot contain any HTML other than frame definitions. It can, however, contain a script. In this script, you can keep window global variables and functions. We will define a minimal string manipulator in a frameset. With this tool you can do the following:
The first two operations merely require calls to string functions. The latter two can be accomplished by routines that we have already written. You store these functions in the frameset of the file c20-7.htm. This frameset requires the files funcs.htm and editor.htm to be in the same directory
The frame named menuFrm will contain buttons to call your frameset functions. These functions must be able to refer to objects in their own frame as well as the adjacent frame editFrm. In addition, you must be able to call these functions from the parent frame. The true value of a frameset function library is its reusability. It is easy to copy the HTML file that defines the library and create a new document by changing a small amount of code-the code that builds the frameset itself. In this way, you can reuse your code
Another way to reuse code is to have all the functions in a small or hidden frame. When you want to use those functions, you simply load that frame. If you take this approach, you don't have to change the frameset code. In both cases, however, it is more difficult to address an adjacent frame than it is to address a parent or child.
The menuFrm frame loads the document defined in the file funcs.htm. This file defines the buttons that access the frameset functions. Some of the object references are quite long, so this file makes liberal use of aliasing to shorten them. In fact, it does it so well that sometimes the whole procedure can be placed in the onClick event handler for the button. The file funcs.htm is loaded into the frames[0] object, while a simple editor window is placed in frames[1]. This editor is implemented as a textarea. When this frameset is loaded, the browser will display something like what is shown in figure 19.4.
FIG. 19.4 A frameset library can be used to implement a string processor
The file funcs.htm also has a test window and a button so you can try out the functions it provides. These functions act on objects in its document. The code is such that the button always calls a routine called Testit. The Testit function has calls to the four routines in the function library. You can easily adapt this code for your own purposes by replacing the Testit function
The most complex part of using functions stored in the frameset is determining the appropriate name of the frameset window. This depends on the window from which the function call is made. The example previously shown offers a very simple solution: just use self.parent.myfunc(). The self portion of this expression can be omitted, but you might want it to discourage ambiguity.
This section presents a working application written entirely in JavaScript. The bugs in this chapter are software bugs, not the kind that crawl. The database is limited to thirty bugs because, at present, the only way to store persistent data is in cookies, which are limited in size and number. You can easily modify the code in this database to use the Image objects or Text objects presented in chapter 9. Indeed, you can even make it a database to hold descriptions of the six-legged kind of bug
Bugs of any kind are usually considered objectionable. You're going to turn them into objects that can be dealt with in an organized fashion. When you design an object, you first ask what information needs to be stored with that object. In the case of a software bug, you might want to track the date the bug was reported, who reported the bug, what the bug is, a description or comments, and the current status of the bug
This is the type of application you might expect to find written in Delphi or Visual Basic. In that case, you would have a great deal of work to do to port the application to other platforms. This application, however, runs in the Netscape Navigator browser window and, thus, will run on any of the platforms Netscape supports. The main shortcoming of this application is that it has no where to store its data except in Netscape cookies, which are limited.
You will need a number of files for this application, which consists of five frames. Make sure you have the following files: c20-8.htm, menu.htm, bugs.htm, function.htm, traymenu.htm, indicatr.htm, bmenu.htm, buginput.htm, and notepad.htm. The top level of this application is, of course, the framesets it uses. Listing 19.9 shows these framesets.
Listing 19.9 The Major Frameset for the Bugs Application <HTML> <HEAD> <!- Created 12 Feb 1996 a6:59 PM 06:59 PM --> <TITLE>Bugs Main Frameset</TITLE> <SCRIPT> //This function is a workaround to make sure that the table overlay is drawn correctly. function forceRewrite() { blankWin=window.open('','blankWin','toobar=no,location=no,directories=no, status=yes, scrollbars=no,resizable=no,copyhistory=no,width=600,height=450') blankWin.close() } </SCRIPT> </HEAD> <FRAMESET ROWS= "80,*" onLoad='forceRewrite()'> <FRAME SRC="menu.htm" NAME="menuFrm" MARGINHEIGHT=3 MARGINWIDTH=3> <FRAME SRC="bugs.htm" NAME="bugsFrm" SCROLLING=YES MARGINHEIGHT=3 MARGINWIDTH=3> <NOFRAMES> You must have a frames-capable browser to <a href=noframes.htm>view this document</a> correctly. </NOFRAMES> </FRAMESET> <SCRIPT> var func = self.frames[0].frames[0] var tray = self.frames[0].frames[1] var smenu = self.frames[1].frames[0] var work = self.frames[1].frames[1] function passDownEdit(which) { smenu.toEditor(which) } </SCRIPT> </HTML>
Before you examine this frameset definition, recall that Netscape draws images in an order that seems unpredictable. Its image drawing order is influenced by the state of your browser and by the size of the images
One way around this is to force a screen refresh by forcing another window to obscure the screen. You can automate this approach by opening and closing another window rapidly above your application to force the redraw. You could use this seemingly worthless window creatively by making it a splash screen and closing it with a timer.
The problem of drawing images in Netscape is described in detail in the "Image as Objects" section of chapter 9.
Script references to child frames or framesets cannot be made until after the framesets are declared. If you want to alias individual frames, do this in a footer script rather than in the header script
We will now examine the structure of the frame declarations. The major frame holds three script items in addition to the frameset. A function called forceRewrite is defined in the header script. This function simply opens and closes a large window. This is done to overcome the image drawing limitation just mentioned. Notice that this function is called in the frameset tag via an onLoad handler. The onLoad handler executes after the page has been completely loaded. In this case, the point of this code is to allow all of the images to be loaded and then force a screen refresh so the table overlays are drawn correctly. Note that both FRAMESET and BODY statements may contain onLoad handlers
The footer script contains aliases to all of the frames that will eventually be loaded. It has to be in the footer because the frameset does not know about its own child frames until you define them. The small footer function passDownEdit is used by the NotePad function to pass a command down to the child window smenu. Its purpose is to simplify window references.
The frameset itself defines two frames. Both of these hold other framesets. Table 19.1 lists the framesets, the frames they contain, the files loaded into those frames, their aliases, and their roles in the overall application. Figure 19.5 shows the page the bug database application creates.
Table 19.1 Definitions for Five Frames, Their Aliases, and Their Functions | |||
---|---|---|---|
Frameset | Frame File | Alias | Function |
menu.htm | functions.htm | func | Holds the function library. |
traymenu.htm | tray | Holds a table overlay with linked image buttons. Each button points to a predefined function name in the bottom-left frame. | |
indicatr.htm | ind | Holds a dummy document that can have messages written to it. | |
bugs.htm | bmnue.htm | smenu | Holds all of the application code. |
buginput.htm | work | Input form for entering bug information. |
FIG. 19.5 When the bug tracking application is loaded it produces this top-level display.
Because you're going to store bugs in your database, you must define a bug object. Because you can easily expect that there will be more than one bug in your database, you must further arrange for an array of bug objects to be created. Listing 19.10 shows the constructor for a single bug object.
Listing 19.10 Constructor Function for a Single Bug function createBug(number,name,date,source,contact,progress,desc) { this.length = 11 this.number = number // a sequential number this.bname = name // name of this bug this.index = '' // current index of this bug in dBugs this.date = date // date first reported this.source = source // what module is bug in this.contact = contact // who reported bug, telephone no this.prog = progress // how far along are you on debugging this.desc = desc // description of bug this.show = showBug // method to make a display string for bug return this }
The array of bug objects will be called dBugs. It will be declared as a global variable. The constructor for this array is shown in listing 19.11. The dBugs object is an extended array. This means you have given the array properties in addition to the length property. You still want to use it as an array, however. As you learned in chapter 9, failure to initialize the entire array will lead to erratic results. Your constructor does initialize it, in fact. This extended array has a number of methods designed to maintain the array and give the user information about the array. Not every method will be used in this application.
See the section on "JavaScript's Associative Arrays" in chapter 9 for a more complete discussion of arrays.
Listing 19.11 The Constructor for the Bug Object Array dBugs function createBugsArray(program) { this.length = 16 this.index = 16 // bug being looked at NOW this.next = 17 // next open slot this.program = program // program being debugged this.seq = 1 // next sequential number this.corrected = 1 // apparent index this.add = addBug // method to add a bug to array this.remove = delTheBug // method to remove a bug to array this.see = seeABug // method to display a bug this.count = countBugs // method to return a count of bugs this.list = listBugs // method to list all bugs this.check = checkName // method to check for unique name this.find = findBug // method to find a bug in array this.pack = packBugs // method to clean up array this.seeMe = seeMyself // method to display the entire array. this.start = 16 // place where array really begins this.length = this.start + 30 var i for (i = this.start; i < this.start + 30; i++) this[i] = 'bug' + i for (i = this.start; i < this.start + 30; i++) this[this[i]] = 'temp ' + i return this }
The first task this application must do is solicit user input. It does this via an input screen in frame[1], which is aliased as work. All of the fields are in the same HTML form and would be accessed as work.document.forms[0]. Therefore, this latter object reference has also been aliased to wrkf, and declared as such in each of the other frames. An application menu (smenu) button calls newBug, which initializes some of the fields of the input form and then allows the user to fill out the form.
Listing 19.12 shows the code for newBug. This function goes through a number of steps to do its work. It checks the flag newInProgress to see if you are already in the process of entering a new bug; if so, it warns the user that it is not allowed and exits. If this test fails, it sets the flag newInProgress to true. It examines a checkbox on the form to see if the user wants the form elements cleared automatically with each new bug entry. It arranges that certain fields will have default values. The user will not be allowed to change the index, the date, or the sequence number fields.
Notice that this function does not make a new bug entry into the dBugs array. You do not do this until the user indicates that he is serious about the entry by clicking the Finished button on the input form.
Listing 19.12 Setting Up for a New Bug Entry function newBug() { if (newInProgress) alert('You have not finished with the last new bug. Only one at ¬ a time, please!') else if (dBugs.next > dBugs.length) alert('You have reached your maximum of 30 bugs. You must fix some ¬ of the bugs which you have recorded before you can enter any more!') else { newInProgress = true wrkf.notxt.value = dBugs.seq wrkf.seqtxt.value= dBugs.next var now = new Date; wrkf.datetxt.value= now.toLocaleString() if (wrkf.autoclchk.checked) work.clearBug() var thisbugname = dBugs.program + dBugs.seq wrkf.nametxt.value= thisbugname wrkf.progbtn[0].checked = true wrkf.progtxt.value= 'new' wrkf.destxt.value = 'New Bug' wrkf.mystatus.value = 'new' } }
When the user does indicate that he is ready to enter the bug, the function saveBug() is called by the input form's Finished button. Note that saveBug has not been made a method of the bug object. However, you can make it into a method by changing all references to dBugs into references to this. You would also have to increase dBugs.start, dBugs.next, and the array length in this case. Listing 19.13 shows the code for the saveBug() function
Listing 19.13 Function to Save a Bug into the dBugs Array and Store it as a Cookie function saveBug() { readVar() var ii = dBugs.next var curbug =new createBug(''+ ii,'bugtemp' + ii,'','','','','') dBugs[ii] = nm dBugs[nm] = curbug dBugs[dBugs[ii]].contact = ct dBugs[dBugs[ii]].source = sr dBugs[dBugs[ii]].prog = pr dBugs[dBugs[ii]].desc = ds dBugs[dBugs[ii]].index = dBugs.next dBugs[dBugs[ii]].number = dBugs.seq dBugs[dBugs[ii]].bname = nm var now = new Date dBugs[dBugs[ii]].date = now dataToCookie(nm,dBugs[dBugs[ii]],dBugs.index) saveSequential() dBugs.add() newInProgress = false updateStatus(ii,'......') flagDone() }
First, saveBug calls a function named readVar(), shown in listing 19.14. The ReadVar() function might seem a little peculiar to you. Why not just read the form values directly into a bug? The reason is a Netscape bug (which might not apply to all platforms and might be fixed by the time you read this). Pulling the input data directly from the form appears to wreak havoc with the dBugs array. The form input value is concatenated with an empty string; this forces it to be interpreted as a string, thereby bypassing this bug. Note that the variables used in readVar are global variables.
Listing 19.14 The Function readVar() Retrieves Input from the Form function readVar() { ct = '' + wrkf.contxt.value sr = '' + wrkf.srctxt.value pr = '' + wrkf.progtxt.value ds = '' + wrkf.destxt.value nm = '' + wrkf.nametxt.value st = '' + wrkf.mystatus.value if (st == 'new') { var badname=true while (badname == true) { badname = dBugs.check(nm) if (badname == true ) { var prst = "This name has already been used." prst += " Please choose another" nm = prompt(prst, nm) } } } }
ReadVar() has a second purpose: it makes sure new bugs have unique bug names. It checks the hidden field mystatus to be sure it is looking at a new bug, rather than an edited bug. In the bug database program, bugs are usually accessed by numerical indexes. The extended array has been set up so you can access a bug by name, as well. If the left side values of an associative array are not unique, this dual form of access is impossible. The code that does the checking calls the dBug.check method, which points to the function checkName(). The checkName() function simply cycles through the left side values of the associative array to see if the name passed to it appears there. If so, it returns true in a variable called badname; if not, it returns false. If badname is true, the user is asked to give it another name, which is again passed to checkName (see fig. 19.6). The cycle continues until a unique name has been found.
FIG. 19.6 Duplicate bug names are flagged by the input processing function
When you have all of the information you need, you can create a new bug object (in the variable curbug). Where this bug should go in the dBugs array is dictated by the statement var ii = dBugs.next. The left side of that array element is filled with nm, the unique name of the bug. The right side of the array is filled with curbug. You could have filled various properties of curbug with information generated by the application or gleaned from the form by readVar() before you placed it into the dBugs array. Instead, we did it the hard way, by adding curbug to the array first and then filling it-just to demonstrate the double indirection into the bug object that is part of the dBugs array. This illustrates clearly that you are dealing with an array of arrays.
The final task is to add the date and then save the bug data in a cookie. The function dataToCookie() rolls up the bug information into a delimited string that can be saved in this manner, as shown in listing 19.15.
Listing 19.15 The Function dataToCookie Condenses a Bug into a Delimited String function dataToCookie(bugname,bug,ndx) { var astr = '' var aname = '@' + bugname var k = bug.length for (i = 1 ; i < k-1 ; i++) { astr += bug[i] + '_' } astr += '$' func.saveOneCookie(aname,astr,'year') }
To save dataToCookie() a little trouble, we pass it the bug name as a separate parameter, although it could be extracted from the bug object itself, which is passed in as the second parameter. The DataToCookie() function prefixes the cookie name with '@' because you probably want to store more than one kind of cookie, and you need a way to tell the various cookies apart. The routine then cycles through each of the bug object's elements (except the last one, which is a method), and concatenates all of the values with the underscore (_) character as the separator.
It ends the string using the dollar sign ($) character as a terminator, and then calls the library function func.saveOneCookie(aname,astr,'year'). Note that this saveOneCookie function is stored in the function library contained in the frames[0] object. frames[0] has been aliased to func. Any function in the function library can be accessed by preceding the method or value name with func. Listing 19.16 shows the code for the saveOneCookie function.
Listing 19.16 The saveOneCookie Function from the Function Library function saveOneCookie(name,what,how) { var expr var cc = how.substring(0,1) cc = cc.toUpperCase() if (cc == 'Y') expr =makeYearExpDate(1) if (cc== 'T') expr = '' if (cc == 'D') expr = makeDeleteExpDate() what = fixSep(what) var astr= name + '=' + what + ';expires=' + expr + ';path=/' document.cookie=astr }
The function saveOneCookie() is important, so we will examine it closely. First, it extracts the first letter of the how parameter and converts it to uppercase. It then tests this value to determine the expiration date for the cookie. If the how parameter has the value 'year', the cookie expiration date is set to one year from the current date. If how is the string 'Temporary', no expiration date is attached to the cookie-the cookie expires at the end of the Netscape session. Finally, if how is 'Delete', the expiration date is set for a time before now. The cookie automatically expires and is removed.
The function fixSep() changes all semicolons (;) to escape codes. The saveOneCookie function arranges to separate all cookies with semicolons, so you don't want any in the cookie body. The bug database application uses its own special separator, but the fixSep() routine can be used generically.
Finally, the cookie is constructed from the value name and what parameters passed to the routine, the expiration date, and the path (which generally is '/'). The cookie is then stored. Remember that document.cookie stores cookies separately but retrieves them jointly.
Because you're assigning each bug an absolute sequential number that isn't dependent on the dBugs array, you need to be able to store this number between invocations of the program. The saveSequential() function simply prefixes the name with a caret (^) to distinguish this cookie from others, defines the cookie, and then calls func.saveOneCookie().
At this point, the bug database application is finished storing the bug. It now has nothing left to do except clean up and notify the user that the bug has been saved. The flag newInProgress is reset to false so a new bug can be entered. A save message is placed in the status bar. However, this is not really an attention-getter, so another mechanism is used as well. You might have wondered about the odd frame in the upper-right corner of the bug database display. This frame is there only to provide the user visual cues. The FlagDone() function rewrites the document of ind to say 'DONE' (vertically) and sets a timer to trigger a 'go back to original' function. The code for this function is shown in listing 19.17. This could be made more elegant if you modified it so it plays a small animated sequence in its tiny window.
Listing 19.17 bmenu.htm A Function To Give User a Visual Cue that Something Has Happened function flagDone() { ind.document.open() ind.document.write('<FONT SIZE=2 COLOR="maroon"><B>') ind.document.write('D<BR>') ind.document.write('O<BR>') ind.document.write('N<BR>') ind.document.write('E<BR>') ind.document.write('</B></FONT>') ind.document.close() TimerID = setTimeout('ind.history.go(-0)',2000) }
Now that you have successfully entered a bug, how do you retrieve it? A crude way is to have the user enter a number at a prompt or, perhaps, in an input field in the application menu. But users rarely remember what numbers are assigned to items, nor should they be required to do so. How about retrieval by name? That's better, but can the user remember the names? The most user-friendly solution is to present the user with a list of names
The most obvious implementation of this list would be a select form element. Unfortunately, this element often misbehaves in current manifestations of Netscape, particularly if there are a large number of items on the list. A hotlist would also be nice. The bug database program produces two kinds of hotlists, in similar ways. The first is invoked by the System Menu List button. This is the first time you have used one of these buttons. Remember that the function of these buttons is to access a function in the resident application code. This function must have a standard name; in this case the function is alist(). The alist() function contains a single line of code:
showInWindow(dBugs.list('<BR>'),'Bugs')
The function showInWindow() puts the string argument given as its first parameter into a pop-up window whose name is given as the second parameter. The dbugs.list() method it calls cycles through the valid dBugs entries and calls the dBugs.see method for each. This method is actually a reference to the function seeABug(), shown in listing 19.18. The resulting hotlist is shown in figure 19.7.
Listing 19.18 The Function seeABug() Converts Bug Information into a String function seeABug(which,trm,cit) { var astr = '' var nstr = '' nstr = this[which] var abug = this[nstr] if (cit) astr = '<A HREF=JavaScript:self.creator.toEditor(' astr += abug.index + ')>' astr += which + ' ' + nstr if (cit) astr += '</A>' astr += trm astr += 'Index: ' + abug.index + trm astr += 'Name: ' + abug.bname + trm astr += 'Number: ' + abug.number + trm astr += 'Date: ' + abug.date + trm astr += 'Source: ' + abug.source + trm astr += 'Contact: ' + abug.contact + trm astr += 'Progress: ' + abug.progress + trm astr += 'Description ' + abug.desc + trm return astr }
FIG. 19.7 The System Menu List button pops up a descriptive hotlist of Bugs
Both the dBugs.list and the dBugs.see methods take a terminator parameter trm, so these functions can produce output that is properly formatted for HTML or for an alert. dBugs.list passes along the terminator to dBugs.see, along with the index of the bug and a variable called cit. If cit is true, dBugs.see will write the first line as a hotlink containing a JavaScript call to a function named toEditor(), along with it's parameters. dbugs.list writes out most of the properties of each bug and presents it in a pop-up window for the user's perusal.
If the user clicks the hotlink, a call is made to the toEditor function, which retrieve the appropriate bug, unwraps it into the input window, and then sets dBugs.index to point to it. If the call came from a window and toEditor() finds that window open, toEditor() closes the window. The toEditor() function also sets the value of the input form's hidden field, mystatus, to 'edit' so saveBug() cannot insist that a new bug name be entered.
The editBug button on the Application menu (a bug with a pencil beside it) performs a similar function, except in this case, the hotlist is composed only of bug names. Each name calls toEditor() if it is clicked. This hotlist is shown in figure 19.8.
FIG. 19.8 A simple hotlist is popped up by the Application menu Edit button
The bugs application lets you edit a bug as many times as you like, so you can update notes and report progress on the bug. It does not have a lot of error checking to ensure that the user enter data properly. You might want to build in the following mechanisms to improve the program:
What do you do when you delete a bug? This implementation of the bug database program does nothing automatically. Ideally, you would archive the bug and then delete it from the list of active bugs. At the moment, Netscape makes no provision for writing to files other than the document cookie, so there is no way to archive the bugs. The Application menu, though, does provide a Delete button so you can delete bugs. The function delTheBug() is used to delete bugs from the dBugs array (see listing 19.19).
Listing 19.19 The Function delTheBug() Deletes a Bug from the dBugs Array and the Document Cookie function delTheBug(which) { if (which < this.start || which >= this.next) { alert('Cannot delete ' + which) return - 1 } else { alert('deleting ' + which) var nstr = this[which] this[which] = '---' this['---'] = '' this.pack() func.saveOneCookie('@' + nstr,'','delete') return 1 } }
This function places '---' in the bug's name and wipes out the bug object referenced by it. It then calls the method dBug.pack(), which eliminates the first '---' it finds in the dBugs array (there should only be one) and moves everything below it up one slot. It then decrements the dBugs.next property. Finally, it calls the func.saveOneCookie() function with the cookie name, an empty string, and the delete flag. This final operation removes the bug from the document cookie.
The Application menu, which is the vertical menu on the left side of the page, has some unexplained items on it. These include a load button, a save button, and a back arrow button. Most of the buttons on the System menu have not been explained, and not all are functional.
The Load button loads all of the bugs from the document.cookie into the dBugs array. It calls a function named cookieToDBug() to do this. cookieToDBug() is also called in the document onLoad event handler so the application starts out with the array loaded.
The Back Arrow button reloads smenu. This is really a debugging function and you might want to remove it in a final application.
The System menu is the horizontal menu on the top of the page. The Forward and Backward Arrow buttons apply to the top window only. If you want to go backward and forward in a frame, you need to supply buttons to do so. These buttons act on the work frame that holds the input form. In the bug database application, this form does not change, so these buttons have no function in this application as it stands.
The Notes button points to a function called aNote() in smenu. If you click the button, a plain little notepad pops up and enables you to save and load notes. Figure 19.9 shows this notepad.
FIG. 19.9 The System menu Notes button pops up a rudimentary notepad.
The Bugs button is meant for debugging purposes during development. It pops up a list of warnings or bugs when an application is finished. At the moment, it points to storeSequential, which often needs to be adjusted during debugging.
The List button, as you have seen, pops up a hotlist that gives information on each bug.
The Links button is meant to pop up a list of links related to what is in the application. It points to the function aLink() in smenu, which is not currently used in this application.
You typically use the Go button to execute an application-specific aGo() function in smenu. In this implementation, aGo pops up an alert with the contents of the document cookie.
The Done button closes the entire application.
The Input Screen is the large data entry area located at the center of the page. The Input screen Forward and Backward buttons scroll backward and forward in the dBugs array. When the user reaches the beginning or end of that portion of dBugs, the user notification area flashes NO.
The User Notification Area is in the upper-right frame; it usually has a picture in it. However, at the end of a load or when a scrolling record hits an end point, it displays a one word message to the user
|