Полезная информация

Chapter 11

Having Fun with JavaScript


CONTENTS


In this chapter you are going to apply some of what you have learned to build a simple application that demonstrates how, with very basic JavaScript, it is possible to create the impression of a sophisticated interactive application.

You are going to design an application that enables users to build their own cartoon faces out of a library of existing eyes, noses, and mouths. No drawing skill is required for the user.

The Specifications

The application has several basic requirements:

Building the Application

In order to build this face program, you need to define the frameset, which contains all the elements of the interface and application.

To do this, use the frameset in Listing 11.1.


Listing 11.1. The parent frameset.
<FRAMESET ROWS="150,150,150,*">

  <FRAMESET COLS="400,*">
    <FRAME SRC="eye1.gif" NAME="eye" MARGINHEIGHT=0
   
MARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="eyes.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAMESET COLS="400,*">
    <FRAME SRC="nose1.gif" NAME="nose" MARGINHEIGHT=0
   
MARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="noses.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAMESET COLS="400,*">
    <FRAME SRC="mouth1.gif" NAME="mouth" MARGINHEIGHT=0
    
MARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="mouths.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAME SRC="build.htm">

</FRAMESET>

This sets up a four-row grid. The top three rows are each divided into two columns: The left side displays the current selection for the eyes, nose, or mouth, and the right side presents all the available choices.

The bottom row is where the control buttons to build the face and generate a random face appear.

Based on this, you need to create four other HTML files (see Listings 11.2 through 11.5) which are the basis of the program: eyes.htm, noses.htm, mouths.htm and build.htm.


Listing 11.2. Source code for eyes.htm.
<!-- SOURCE CODE FOR eyes.htm -->

<HTML>

<BODY BGCOLOR="iceblue">

  <TABLE BORDER=0>

    <TR>

      <TD><A HREF="eye1.gif" TARGET="eye">
      <IMG SRC="eye1sample.gif" BORDER=0></A></TD>

      <TD><A HREF="eye2.gif" TARGET="eye">
      <IMG SRC="eye2sample.gif" BORDER=0></A></TD>

    </TR>

    <TR>

      <TD><A HREF="eye3.gif" TARGET="eye"">
      <IMG SRC="eye3sample.gif" BORDER=0></A></TD>

      <TD><A HREF="eye4.gif" TARGET="eye">
      <IMG SRC="eye4sample.gif" BORDER=0></A></TD>

    </TR>

  </TABLE>

</BODY>

<
/HTML>


Listing 11.3. The source code for noses.htm.
<!-- SOURCE CODE FOR noses.htm -->

<HTML>

<BODY BGCOLOR="iceblue">

  <TABLE BORDER=0>

    <TR>

      <TD><A HREF="nose1.gif" TARGET="nose">
      <IMG SRC="nose1sample.gif" BORDER=0></A></TD>

      <TD><A HREF="nose2.gif" TARGET="nose">
      <IMG SRC="nose2sample.gif" BORDER=0></A></TD>

    </TR>

    <TR>

      <TD><A HREF="nose3.gif" TARGET="nose">
      <IMG SRC="nose3sample.gif" BORDER=0></A></TD>

      <TD><A HREF="nose4.gif" TAGRET="nose">
      <IMG SRC="nose4sample.gif" BORDER=0></A></TD>

    </TR>

  </TABLE>

</BODY>

</HTML>


Listing 11.4. The source code for mouths.htm.
<!-- SOURCE CODE FOR mouths.htm -->

<HTML>

<BODY BGCOLOR="iceblue">

  <TABLE BORDER=0>

    <TR>

      <TD><A HREF="mouth1.gif" TARGET="mouth">
      <IMG SRC="mouth1sample.gif" BORDER=0></A></TD>

      <TD><A HREF="mouth2.gif" TAGRET="mouth">
      <IMG SRC="mouth2sample.gif" BORDER=0></A></TD>

    </TR>

    <TR>

      <TD><A HREF="mouth3.gif" TARGET="mouth">
      <IMG SRC="mouth3sample.gif" BORDER=0></A></TD>

      <TD><A HREF="mouth4.gif" TARGET="mouth">
      <IMG SRC="mouth4sample.gif" BORDER=0></A></TD>

    </TR>

  </TABLE>

</BODY>

</HTML>

The file build.htm (Listing 11.5) provides the controls in the bottom frame:


Listing 11.5. The source code for build.htm.
<!-- SOURCE CODE FOR build.htm -->

<HTML>

<HEAD>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

// Build the complete face in a separate window
function buildFace() {

  var eye = parent.eye.location;
  var nose = parent.nose.location;
  var mouth = parent.mouth.location;

  var face = window.open("","builtFace","width=400,height=450");
  face.document.open("text/html");
  face.document.write('<IMG SRC="' + eye + '">');
  face.document.write('<IMG SRC="' + nose + '">');
  face.document.write('<IMG SRC="' + mouth + '">');
  face.document.close();

}

// Build a random face in the current window
function randomFace() {

  var eye = "eye" + getRandom() + ".gif";
  var nose = "nose" + getRandom() + ".gif";
  var mouth = "mouth" + getRandom() + ".gif";

parent.eye.location = eye;
parent.nose.location = nose;
parent.mouth.location = mouth;

}

// Generate a random number
function getRandom() {

  today = new Date();
  var bigNumber = today.getSeconds() * today.getTime() *
     ÂMath.sqrt(today.getMinutes());
  var randomNum = (bigNumber % 4) + 1;

  return Math.floor(randomNum);

}

// STOP HIDING -->
</SCRIPT>

</HEAD>

<BODY BGCOLOR="#000000" TEXT="iceblue">
<FORM METHOD=POST>
<CENTER>
<INPUT TYPE="button" VALUE="Build This Face" onClick="buildFace();">
<INPUT TYPE="button" VALUE="Make A Random Face" onClick="randomFace();">
</CENTER>
</FORM>
</BODY>

</HTML>


The results of this application appear similar to Figures 11.1 and 11.2.

Figure 11.1 : Choosing a facial feature updates the relevant frame on the left.

Figure 11.2 : Clicking on the build button causes a new window to open.

The first thing to notice about the four HTML documents is that three of them-eyes.htm, noses.htm, and mouths.htm-are very similar.

This is because all three files play the same role: They present options for the user to select each of three parts of the face.

Each file displays four options in a 2¥2 table. Each of the small images displayed is, in fact, a link targeted to the appropriate frame on the left. When the user clicks on one of the sample images, the full-size version of that feature is displayed in the corresponding frame on the left.

You use JavaScript in the build.htm document. Here you have three functions related to the two buttons displayed in the bottom frame of the frameset: buildFace(), randomFace(), and getRandom().

buildFace() and randomFace() are called by the event handlers of the two buttons:

<INPUT TYPE="button" VALUE="Build This Face" onClick="buildFace();">
<INPUT TYPE="button" VALUE="Make A Random Face" onClick="randomFace();">

The buildFace() Function

The buildFace() function takes the three pieces of the face in the three frames and displays them in a single new window. This function needs to find out which files are displayed in each of the three left frames and then builds a single HTML file that displays the three files in a single window.

function buildFace() {

  var eye = parent.eye.location;
  var nose = parent.nose.location;
  var mouth = parent.mouth.location;

  var face = window.open("","builtFace","width=400,height=450");
  face.document.open("text/html");
  face.document.write('<IMG SRC="' + eye + '">');
  face.document.write('<IMG SRC="' + nose + '">');
  face.document.write('<IMG SRC="' + mouth + '">');
  face.document.close();

}

The function starts by getting the URLs of the three selected facial features with the location property of the frame object, as in the example var eye = parent.eye.location;. You use the fact that you can address object properties by name in the structure parent.eye to reference each frame.

Once this is done, a new window of the desired size is opened using

var face =window.open("","builtFace","width=400,height=450");

This command specifies the size of the window in pixels using the optional windows attributes argument. The new object for this window is called face so that you can later use commands such as face.document.write().

After the window is open, open an HTML output stream using face.document.open("text/html") and then write out the three image tags based on the URLs you got earlier. Finally, you close the document output stream.

The randomFace() Function

The randomFace() function simply selects three random facial features and then opens them in the appropriate frames. This is done by calling the getRandom() function, which returns a number from 1 to 4, and then building three filenames, such as

var eye = "eye" + getRandom() + ".gif".
function randomFace() {

  var eye = "eye" + getRandom() + ".gif";
  var nose = "nose" + getRandom() + ".gif";
  var mouth = "mouth" + getRandom() + ".gif";

parent.eye.location = eye;
parent.nose.location = nose;
parent.mouth.location = mouth;

}

Once this is done, the three files are opened using window.open().

The getRandom() Function

The getRandom() function is designed to return a random number from one to four. This is done by creating a new Date object for the current date and time, getting a large number by multiplying together different elements of the Date object, and then taking the modulus by four and adding one, as shown in the following lines.

function getRandom() {

  today = new Date();
  var bigNumber = today.getSeconds() * today.getTime() *
    
Math.sqrt(today.getMinutes());
  var randomNum = (bigNumber % 4) + 1;

  return Math.floor(randomNum);

}

The result is a pseudo-random number from one to four (because the modulus returns a number greater than or equal to zero and less than four). The use of Math.floor() ensures that the number returned is an integer.

Note
In Navigator 3, the Math.random() method could provide a random number. However, this will not work on all platforms in Navigator 2.

Moving Beyond the Basic Script

There are a couple of limitations to the current script that you might like to improve:

More Than Four Choices

In order to expand the script to support more than four choices for each feature, you need to make changes to each of the HTML files, including the parent frameset.

You start by adding a small script to the parent frameset (Listing 11.1).

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

var numEyes = 4;
var numNoses = 4;
var numMouths = 4;

// STOP HIDING -->
</SCRIPT>

This script is where you set the number of options for each of the three facial features. That way, if you want to add or remove choices, you only need to change these three numbers, and the whole program will work.

Next, you need to alter the three files eyes.htm, noses.htm, and mouths.htm (Listings 11.2 through 11.4) so that you use JavaScript to dynamically build two-row tables, regardless of the number of choices.

By way of example, this is what eyes.htm would look like:

<BODY BGCOLOR="iceblue">

  <TABLE BORDER=0>

    <TR>

    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FORM OTHER BROWSERS

    for (var i = 1; i <= Math.floor(parent.numEyes / 2); i ++) {
      document.write('<TD><A HREF="eye' + i + '.gif" TARGET="eye">');

      document.write('<IMG SRC="eye' + i + 'sample.gif" BORDER=0></A></TD>');
    }

    // STOP HIDING -->
    </SCRIPT>

    </TR>

    <TR>

    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FROM OTHER BROWSERS

    for (var i = Math.floor(parent.numEyes / 2) + 1;
  
i <= parent.numEyes; i ++) {
      document.write('<TD><A HREF="eye' + i + '.gif" TARGET="eye">');

      document.write('<IMG SRC="eye' + i + 'sample.gif" BORDER=0></A></TD>');
    }

    // STOP HIDING -->
    </SCRIPT>

    </TR>

  </TABLE>

</BODY>

What you have added are two short scripts that build the table cells for each row of the table. The first script builds cells for each image from the first to the halfway point in the available list, and the second builds from there to the end. You use Math.floor() to ensure that you are building filenames out of integer values.

Finally, you need to alter the randomFace() and getRandom() functions in build.htm (Listing 11.5). getRandom() now takes an argument which is the range it is supposed to return (that is, from 1 to num). randomFace() passes the appropriate variable from the parent frameset for each function call to getRandom().

function randomFace() {

  var eye = "eye" + getRandom(parent.numEyes) + ".gif";
  var nose = "nose" + getRandom(parent.numNoses) + ".gif";
  var mouth = "mouth" + getRandom(parent.numMouths) + ".gif";

parent.eye.location = eye;
parent.nose.location = nose;
parent.mouth.location = mouth;

}

function getRandom(num) {

  today = new Date();
  var bigNumber = today.getSeconds() * today.getTime() *
    
Math.sqrt(today.getMinutes());
  var randomNum = bigNumber % num + 1;

  return Math.floor(randomNum);

}

Building Faces in Multiple Windows

In order to build each face in a new window, you need to make far fewer changes than you made to support the variable number of choices for each facial attribute.

All the changes are made to the file build.htm. You add a global variable called windowNumber, which you increment for each window you open. Then you make one change to the function buildFace() on the line where you open the window:

var face = window.open("","builtFace" + windowNumber ++,"width=400,height=450");

This builds a new window name based on the value of windowNumber and then increments windowNumber by one. Remember that the unary increment operator (++) after an expression first evaluates the expression (in this case, that is simply windowNumber) and then increments the expression.

Note
Navigator includes a special target _blank which, when used in conjunction with window.open(), opens a new window. For instance, window.open("","_blank") opens a new empty window.

Summary

To help you see all the changes you have made, here is the source code including all the changes. I am only including eyes.htm (and not noses.htm or mouths.htm) because the changes are the same in these three files.


Listing 11.6. Final version of the application.
<!-- SOURCE CODE FOR PARENT FRAMESET -->


<HEAD>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

var numEyes = 4;
var numNoses = 4;
var numMouths = 4;

// STOP HIDING -->
</SCRIPT>

</HEAD>

<FRAMESET ROWS="150,150,150,*">

  <FRAMESET COLS="400,*">
    <FRAME SRC="eye1.gif" NAME="eye" MARGINHEIGHT=0
  
MARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="eyes.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAMESET COLS="400,*">
    <FRAME SRC="nose1.gif" NAME="nose" MARGINHEIGHT=0
  
MARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="noses.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAMESET COLS="400,*">
    <FRAME SRC="mouth1.gif" NAME="mouth" MARGINHEIGHT=0
  
MARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="mouths.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAME SRC="build.htm">

</FRAMESET>
<!-- SOURCE CODE FOR build.html -->

<HEAD>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

var windowNumber = 1;

// Build the face in a separate window
function buildFace() {

  var eye = parent.eye.location;
  var nose = parent.nose.location;
  var mouth = parent.mouth.location;

  var face = window.open("","builtFace" +
    
windowNumber ++,"width=400,height=450");
  face.document.open("text/html");
  face.document.write('<IMG SRC="' + eye + '">');
  face.document.write('<IMG SRC="' + nose + '">');
  face.document.write('<IMG SRC="' + mouth + '">');
  face.document.close();

}

// Display a random face in the current window
function randomFace() {

  var eye = "eye" + getRandom(parent.numEyes) + ".gif";
  var nose = "nose" + getRandom(parent.numNoses) + ".gif";
  var mouth = "mouth" + getRandom(parent.numMouths) + ".gif";

parent.eye.location = eye;
parent.nose.location = nose;
parent.mouth.location = mouth;

}

// Generate a random number
function getRandom(num) {

  today = new Date();
  var bigNumber = today.getSeconds() * today.getTime() *
    
Math.sqrt(today.getMinutes());
  var randomNum = Math.floor(bigNumber % num);

  return randomNum + 1;

}

// STOP HIDING -->
</SCRIPT>

</HEAD>

<BODY BGCOLOR="#000000" TEXT="iceblue">
<FORM METHOD=POST>
<CENTER>
<INPUT TYPE="button" VALUE="Build This Face" onClick="buildFace();">
<INPUT TYPE="button" VALUE="Make A Random Face" onClick="randomFace();">
</CENTER>
</FORM>
</BODY>
<!-- SOURCE CODE FOR eyes.html -->

<BODY BGCOLOR="iceblue">

  <TABLE BORDER=0>

    <TR>

    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FORM OTHER BROWSERS

    for (var i = 1; i <= Math.floor(parent.numEyes / 2); i ++) {
      document.write('<TD><A HREF="eye' + i + '.gif" TARGET="eye">');

      document.write('<IMG SRC="eye' + i + 'sample.gif" BORDER=0></A></TD>');
    }

    // STOP HIDING -->
    </SCRIPT>

    </TR>

    <TR>

    <SCRIPT LANGUAGE="JavaScript">
    <!-- HIDE FROM OTHER BROWSERS

    for (var i = Math.floor(parent.numEyes / 2) + 1;
  
i <= parent.numEyes; i ++) {
document.write('<TD><A HREF="eye' + i + '.gif" TARGET="eye">');

      document.write('<IMG SRC="eye' + i + 'sample.gif" BORDER=0></A></TD>');
    }

    // STOP HIDING -->
    </SCRIPT>

    </TR>

  </TABLE>

</BODY>

Exercise

  1. We still have one more addition to make: the ability to save a face and have it displayed as the first face when the user returns to the site. Extend the application so that there is a third button at the bottom to save the current face and change the loading procedure so that the saved face is loaded each time the user arrives at the site.

Answer

  1. As you might have expected, the solution lies in cookies. In the file build.htm, you need to add a function called saveFace(), which saves the three filenames in three separate cookies. Define the path for the cookies to be "/" so all the files in your application have access to the cookies. Use Bill Dortch's cookie functions to handle the dirty work of saving and retrieving the cookies.
Next, extend the parent frameset file so that it loads a blank file into each of three frames on the left and then uses an onLoad event handler to call a function called loadFace() to retrieve the relevant cookies and load the files into the empty frames.
The resulting HTML documents look like Listing 11.7.


Listing 11.7. The final version of the parent frameset.
<!-- SOURCE CODE OF PARENT FRAMESET -->

<HEAD>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

//
//  Cookie Functions - Second Helping  (21-Jan-96)
//  Written by:  Bill Dortch, hIdaho Design <bdortch@netw.com>
//  The following functions are released to the public domain.
//
// "Internal" function to return the decoded value of a cookie
//
function getCookieVal (offset) {
  var endstr = document.cookie.indexOf (";", offset);
  if (endstr == -1)
    endstr = document.cookie.length;
  return unescape(document.cookie.substring(offset, endstr));
}

//
//  Function to return the value of the cookie specified by "name".
//    name - String object containing the cookie name.
//    returns - String object containing the cookie value, or null if
//      the cookie does not exist.
//
function GetCookie (name) {
  var arg = name + "=";
  var alen = arg.length;
  var clen = document.cookie.length;
  var i = 0;
  while (i < clen) {
    var j = i + alen;
    if (document.cookie.substring(i, j) == arg)
      return getCookieVal (j);
    i = document.cookie.indexOf(" ", i) + 1;
    if (i == 0) break;
  }
  return null;
}

//
//  Function to create or update a cookie.
//    name - String object object containing the cookie name.

//    value - String object containing the cookie value.  May contain
//      any valid string characters.
//    [expires] - Date object containing the expiration data of the cookie.  
//    If omitted or null, expires the cookie at the end of the current
  Âsession.
//    [path] - String object indicating the path for which the cookie is
  Âvalid.
//      If omitted or null, uses the path of the calling document.
//    [domain] - String object indicating the domain for which the cookie
//    is valid.  If omitted or null, uses the domain of the calling
  Âdocument.
//    [secure] - Boolean (true/false) value
  Âindicating whether cookie transmission
//      requires a secure channel (HTTPS).
//
//  The first two parameters are required.  The others, if supplied, must
//  be passed in the order listed above.  To omit an unused optional field,
//  use null as a place holder.  For example, to call SetCookie using name,
//  value and path, you would code:
//
//      SetCookie ("myCookieName", "myCookieValue", null, "/");
//
//  Note that trailing omitted parameters do not require a placeholder.
//
//  To set a secure cookie for path "/myPath", that expires after the
//  current session, you might code:
//
//      SetCookie (myCookieVar, cookieValueVar, null, "/myPath", null,
      Âtrue);
//
function SetCookie (name, value) {
  var argv = SetCookie.arguments;
  var argc = SetCookie.arguments.length;
  var expires = (argc > 2) ? argv[2] : null;
  var path = (argc > 3) ? argv[3] : null;
  var domain = (argc > 4) ? argv[4] : null;
  var secure = (argc > 5) ? argv[5] : false;
  document.cookie = name + "=" + escape (value) +
    ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) +
    ((path == null) ? "" : ("; path=" + path)) +
    ((domain == null) ? "" : ("; domain=" + domain)) +
    ((secure == true) ? "; secure" : "");
}

//  Function to delete a cookie. (Sets expiration date to current date/
         Âtime)
//    name - String object containing the cookie name
//
function DeleteCookie (name) {
  var exp = new Date();
  exp.setTime (exp.getTime() - 1);  // This cookie is history
  var cval = GetCookie (name);
  document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString();
}

var numEyes = 4;
var numNoses = 4;
var numMouths = 4;

function loadFace() {

      var eye = GetCookie("eye");
      if ((eye == null) || (eye == "")) { eye = "eye1.gif"; }
      parent.eye.location = eye;

      var nose = GetCookie("nose");
      if ((nose == null) || (nose == "")) { nose = "nose1.gif"; }
      parent.nose.location = nose;

      var mouth = GetCookie("mouth");
      if ((mouth == null) || (mouth == "")) { mouth = "mouth1.gif"; }
      parent.mouth.location = mouth;

}

// STOP HIDING -->
</SCRIPT>

</HEAD>

<FRAMESET ROWS="150,150,150,*" onLoad="loadFace();">

  <FRAMESET COLS="400,*">
    <FRAME SRC="blank.htm" NAME="eye" MARGINHEIGHT=0
         ÂMARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="eyes.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAMESET COLS="400,*">
    <FRAME SRC="blank.htm" NAME="nose" MARGINHEIGHT=0
         ÂMARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="noses.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="auto">
  </FRAMESET>

  <FRAMESET COLS="400,*">
    <FRAME SRC="blank.htm" NAME="mouth" MARGINHEIGHT=0
         ÂMARGINWIDTH=0 SCROLLING="no">
    <FRAME SRC="mouths.htm" MARGINHEIGHT=0 MARGINWIDTH=0 SCROLLING="no">
  </FRAMESET>

  <FRAME SRC="build.htm">

  <SCRIPT LANGUAGE="JavaScript">
  <!-- HIDE FROM OTHER BROWSERS


  // STOP HIDING -->
  </SCRIPT>

</FRAMESET>


Listing 11.8. Revised version of build.htm.
<!-- SOURCE CODE FOR build.html -->

<HEAD>

<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS

//
//  Cookie Functions - Second Helping  (21-Jan-96)
//  Written by:  Bill Dortch, hIdaho Design <bdortch@netw.com>
//  The following functions are released to the public domain.
//
//
// "Internal" function to return the decoded value of a cookie
//
function getCookieVal (offset) {
  var endstr = document.cookie.indexOf (";", offset);
  if (endstr == -1)
    endstr = document.cookie.length;
  return unescape(document.cookie.substring(offset, endstr));
}

//
//  Function to return the value of the cookie specified by "name".
//    name - String object containing the cookie name.
//    returns - String object containing the cookie value, or null if
//      the cookie does not exist.
//
function GetCookie (name) {
  var arg = name + "=";
  var alen = arg.length;
  var clen = document.cookie.length;
  var i = 0;
  while (i < clen) {
    var j = i + alen;
    if (document.cookie.substring(i, j) == arg)
      return getCookieVal (j);
    i = document.cookie.indexOf(" ", i) + 1;
    if (i == 0) break;
  }
  return null;
}

//
//  Function to create or update a cookie.
//    name - String object object containing the cookie name.

//    value - String object containing the cookie value.  May contain
//      any valid string characters.
//    [expires] - Date object containing the expiration data of the cookie.  
//      If omitted or null, expires the cookie at the end of the
//    current session.
//    [path] - String object indicating the path for which the cookie is
//      valid.
//      If omitted or null, uses the path of the calling document.
//    [domain] - String object indicating the domain for which the cookie
//      is valid.  If omitted or null, uses the domain of the calling
//   document.
//    [secure] - Boolean (true/false) value
//   indicating whether cookie transmission
//      requires a secure channel (HTTPS).
//
//  The first two parameters are required.  The others, if supplied, must
//  be passed in the order listed above.  To omit an unused optional field,
//  use null as a place holder.  For example, to call SetCookie using name,
//  value and path, you would code:
//
//      SetCookie ("myCookieName", "myCookieValue", null, "/");
//
//  Note that trailing omitted parameters do not require a placeholder.
//
//  To set a secure cookie for path "/myPath", that expires after the
//  current session, you might code:
//
//      SetCookie (myCookieVar, cookieValueVar, null, "/myPath", null,
//     true);
//
function SetCookie (name, value) {
  var argv = SetCookie.arguments;
  var argc = SetCookie.arguments.length;
  var expires = (argc > 2) ? argv[2] : null;
  var path = (argc > 3) ? argv[3] : null;
  var domain = (argc > 4) ? argv[4] : null;
  var secure = (argc > 5) ? argv[5] : false;
  document.cookie = name + "=" + escape (value) +
    ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) +
    ((path == null) ? "" : ("; path=" + path)) +
    ((domain == null) ? "" : ("; domain=" + domain)) +
    ((secure == true) ? "; secure" : "");
}

//  Function to delete a cookie. (Sets expiration date to current date/
//  time)
//    name - String object containing the cookie name
//
function DeleteCookie (name) {
  var exp = new Date();
  exp.setTime (exp.getTime() - 1);  // This cookie is history
  var cval = GetCookie (name);
  document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString();
}

var windowNumber = 1;

function buildFace() {

  var eye = parent.eye.location;
  var nose = parent.nose.location;
  var mouth = parent.mouth.location;

  var face = window.open("","builtFace" +
    
windowNumber ++,"width=400,height=450");
  face.document.open("text/html");
  face.document.write('<IMG SRC="' + eye + '">');
  face.document.write('<IMG SRC="' + nose + '">');
  face.document.write('<IMG SRC="' + mouth + '">');
  face.document.close();

}

function randomFace() {

  var eye = "eye" + getRandom(parent.numEyes) + ".gif";
  var nose = "nose" + getRandom(parent.numNoses) + ".gif";
  var mouth = "mouth" + getRandom(parent.numMouths) + ".gif";

parent.eye.location = eye;
parent.nose.location = nose;
parent.mouth.location = mouth;

}

function getRandom(num) {

  today = new Date();
  var bigNumber = today.getSeconds() * today.getTime() *
    
Math.sqrt(today.getMinutes());
  var randomNum = Math.floor(bigNumber % num);

  return randomNum + 1;

}

function saveFace() {

  var eye = parent["eye"].location;
  var nose = parent["nose"].location;
  var mouth = parent["mouth"].location;

  var expiry = new Date;
  expiry.setTime(expiry.getTime() + 365*24*60*60*1000);

  SetCookie("eye",eye,expiry,"/");
  SetCookie("nose",nose,expiry,"/");
  SetCookie("mouth",mouth,expiry,"/");

}

// STOP HIDING -->
</SCRIPT>

</HEAD>

<BODY BGCOLOR="#000000" TEXT="iceblue">
<FORM METHOD=POST>
<CENTER>
<INPUT TYPE="button" VALUE="Build This Face" onClick="buildFace();">
<INPUT TYPE="button" VALUE="Make A Random Face" onClick="randomFace();">
<INPUT TYPE="button" VALUE="Save This Face" onClick="saveFace();">
</CENTER>
</FORM>
</BODY>

You may also want to extend the form in build.htm to add a button to load a saved face:

<INPUT TYPE="button" VALUE="Load Saved Face" onClick="parent.loadFace();">