/*=====================================================================================
                                    fs_utils.js

  This file contains utilitiy functions used by various pieces of the course content.
  It should be included into the frameset.htm file. The functions contain context-sensitive
  references to window properties, and therefore will generally not work correctly if fs_utils.js
  is included into any file other than frameset.htm.
  =====================================================================================*/

document.write("<script src='../_CourseCommon/NewNotebookNames.js'></script>");

var isSafari = (navigator.userAgent.toLowerCase().lastIndexOf("safari") >= 0);
var isNS  = (window.navigator.appName.indexOf("Netscape") >= 0);
var isIE  = (window.navigator.appName.indexOf("Explorer") >= 0);
var isWin  = (window.navigator.userAgent.indexOf("Win") >= 0); 
var isMac  = (window.navigator.userAgent.indexOf("Mac") >= 0);

//--------------------------------------------------------------------
//gets base url or CD root for images in notebooks
// [IS THIS FUNCTION USED??  -RJB]
var LOAD_PAGE_WINDOW = new Array()//A global variable used with LoadPage, when run in Safari.
							 	  //It stores the window object passed in the last call to LoadPage.
var LOAD_PAGE_URL    = new Array()//Used in the safari version of LoadPage
var LOAD_PAGE_TIME
var LOAD_PAGE_WINDOW_INDEX = 0
var INTERVAL_ID = null
		 								  
function get_url_add () {
  if (useCD == true) {
    url_add = CDRoot;
    //return url_add;
  }
  else {
    url_add = folderURL(self)
    base_url = url_add.substring(0, url_add.length-1);
    //return url_add;
  }
}
//--------------------------------------------------------------------
// plays audio files with paths from exams
function playSound(movie){
  if (isWin && isNS) {      
    if (useCD == true) {
      window.status="Playing audio from CD"
      window.aux1.document.write('<EMBED SRC="' + CDRoot + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();
    }
    else {
      window.status="Playing audio from course server"
      base_url = folderURL(self)
      base_url = base_url.substring(0, base_url.length-1)
      movie_s = base_url + "/" + movie;
      window.aux1.document.write('<EMBED SRC="' + movie_s + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();    
    }
  }
  else {
    if (useCD == true) {
      window.status="Playing audio from CD"
      window.aux1.document.write('<EMBED SRC="' + CDRoot + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();
    }
    else {
      window.status="Playing audio from course server"
      base_url = folderURL(self)
      base_url = base_url.substring(0, base_url.length-1)
      movie_s = base_url + "/" + movie;
      window.aux1.document.write('<EMBED SRC="' + movie_s + '" autoplay=true width=16 height=16 controller=false  type="video/quicktime"></embed>')
      window.aux1.document.close();    
    }   
  }
  status_timer = setTimeout("clear_status()",10000);
}

//--------------------------------------------------------------------
//plays audio files from frameset with a full path from root
function playSoundFrameset(movie){
  base_url = folderURL(self)
  base_url = base_url.substring(0, base_url.length-1)

  if (isWin && isNS) {
    if (useCD == true) {
      window.status="Playing audio from CD"
      window.aux1.document.write('<EMBED SRC="' + CDRoot + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();
    }
    else {
      window.status="Playing audio from course server"
      window.aux1.document.write('<EMBED SRC="' + base_url + "/" + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();    
    }
  }
  else {
    if (useCD == true) {
      window.status="Playing audio from CD"
      window.aux1.document.write('<EMBED SRC="' + CDRoot + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();
    }
    else {
      window.status="Playing audio from course server"
      window.aux1.document.write('<EMBED SRC="' + base_url + "/" + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();    
    }   
  }
  status_timer = setTimeout("clear_status()",10000);
}

//--------------------------------------------------------------------
//plays audio files from frameset without a full path from root 
function playSoundFrameset2(movie){

  mybase = ""

  base_url = main.location.href.substring(0,main.location.href.lastIndexOf("/"))
  base_string = main.location.pathname.substring(0,main.location.pathname.lastIndexOf("/"))
  base = base_string.split("/")
  for (i = MEDIA_DEPTH; i < base.length;  i++) {
    mybase += base[i]  + "/"  
  }

  if (isWin && isNS) {       
    if (useCD == true) {
      window.aux1.document.write('<EMBED SRC="' + base_url + "/" + movie+ '" width="16" height="16" controller="false" autoplay="true" type="video/quicktime"></EMBED>')
      window.aux1.document.close();
    }
    else {
      window.aux1.document.write('<EMBED SRC="' + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();    
    }
  }
  else {
    if (useCD == true) {
      window.aux1.document.write('<EMBED SRC="' + CDRoot + mybase +movie+  '" width="16" height="16" controller="false" autoplay="true" type="video/quicktime"></EMBED>')
      window.aux1.document.close();
    }
    else {
      window.aux1.document.write('<EMBED SRC="' + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
      window.aux1.document.close();    
    }   
  }
}
//--------------------------------------------------------------------
// plays video files from popups in frameset
function playVideo(movie,w,h){
  w_height = h + 70;
  w_width = w + 10;
  h = h + 10
  if (useCD == true) {
    m = window.open("","new_win","height=" + w_height + ",width=" + w_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no")
    m.document.write('<html><head><title>Movie Window</title></head><body bgcolor="white" marginwidth="0" marginheight="0" topmargin="0" leftmargin="0"><center><EMBED SRC="' + CDRoot + movie + '" autoplay=true width= "' + w + '" height= "' + h + '" controller=true type="video/quicktime"></embed></center><center><form><INPUT TYPE="button" name = "close" value="Close window" onclick = "self.close()"></form></center></body></html>')
  }
  else {
    base_url = folderURL(self)
    base_url = base_url.substring(0, base_url.length-1)
    movie_s = base_url + "/" + movie;
    m = window.open("","new_win","height=" + w_height + ",width=" + w_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no")
    m.document.write('<html><head><base href="/"><title>Movie Window</title></head><body bgcolor="white" marginwidth="0" marginheight="0" topmargin="0" leftmargin="0"><center><EMBED SRC="' + movie_s + '" autoplay=true width= "' + w + '" height= "' + h + '" controller=true type="video/quicktime"></embed></center><center><form><INPUT TYPE="button" name = "close" value="Close window" onclick = "self.close()"></form></center></body></html>')
  }
}
//--------------------------------------------------------------------
//change 8/14/2001 to replace old regex that may not work with other url constructions
//old function used in amhist1a
function popPlaySound(movie){

  // I have doubts about the following "if" statement.  I am not sure that the base_string
  // *really* should be different depending on the XML_MENU case.  I'm just copying this
  // from possibly buggy logic that I inherited and don't understand 100%.  - RJB
  if (XML_MENU)
    base_string = window.main.location.pathname.substring(0,main.location.pathname.lastIndexOf("/"))
  else
  {
    base_string = folderURL(self)
    base_string = base_string.substring(0, base_string.length-1) //Chop trailing "/"
  }

  if (window.opener.parent.isWin && window.opener.parent.isNS) {    

    if (window.opener.parent.useCD == true) {
      mybase = ""
      base = base_string.split("/")
      for (i = MEDIA_DEPTH; i < base.length;  i++) {
        mybase += base[i]  + "/"  
      }
      movie_url = mybase + movie;
 
      window.opener.parent.aux1.document.open()
      window.opener.parent.aux1.document.write("<EMBED SRC='"+ window.opener.parent.CDRoot + movie_url+"' width=16 height=16 controller='false' autoplay='true' type='video/quicktime'></embed>")
      putontop = setTimeout("window.focus()",1000);
      window.opener.parent.aux1.document.close()
    }
    else {
      window.opener.parent.aux1.document.open()
      window.opener.parent.aux1.document.write("<EMBED SRC='"+movie+"' width=16 height=16 controller='false' autoplay='true' type='video/quicktime'></embed>")
      window.opener.parent.aux1.document.close()
      putontop = setTimeout("window.focus()",1000);
    }
  }
  else {
    if (window.opener.parent.useCD == true) {
      mybase = ""
      base = base_string.split("/")
      for (i = MEDIA_DEPTH; i < base.length;  i++) {
        mybase += base[i]  + "/"  
      }
      movie_url = mybase + movie;
 
      window.opener.parent.aux1.document.open()
      window.opener.parent.aux1.document.write("<EMBED SRC='"+ window.opener.parent.CDRoot + movie_url+"' width=16 height=16 controller='false' autoplay='true' type='video/quicktime'></embed>")
      window.opener.parent.aux1.document.close()
      putontop = setTimeout("window.focus()",1000);
    }
    else {
      window.opener.parent.aux1.document.open()
      window.opener.parent.aux1.document.write("<EMBED SRC='"+movie+"' width=16 height=16 controller='false' autoplay='true' type='video/quicktime'></embed>")
      window.opener.parent.aux1.document.close()
      putontop = setTimeout("window.focus()",1000);
    }
  }
}

//--------------------------------------------------------------------
// plays audio files without a path from exams
// may replace NET script which places a media folder name in path

function playSound2(movie){

  var md
  mybase = ""

  if (XML_MENU) {
    base_string = window.main.location.pathname.substring(0,main.location.pathname.lastIndexOf("/"))
    base_url = window.main.document.location.href.substring(0,window.main.document.location.href.lastIndexOf("/"))
    md = 4
  }
  else {
    base_url = main.location.href.substring(0,main.location.href.lastIndexOf("/"))
    base_string = main.location.pathname.substring(0,main.location.pathname.lastIndexOf("/"))
    md = MEDIA_DEPTH
  }

  base = base_string.split("/")
  for (i = md; i < base.length;  i++) {
    mybase += base[i]  + "/"  
  }
  if (window.isWin && window.isNS) {    
    if (window.useCD == true) {
      window.status="Playing audio from CD"
      window.aux1.document.write('<EMBED SRC="' + CDRoot + mybase +movie+  '" width="16" height="16" controller="false" autoplay="true" type="video/quicktime"></EMBED>')
    }
    else
    {
      window.status="Playing audio from course server"
      window.aux1.document.write('<EMBED SRC="' + base_url + "/" + movie+ '" width="16" height="16" controller="false" autoplay="true" type="video/quicktime"></EMBED>')
    }
  }
  else {
    if (window.useCD == true) {
      window.status="Playing audio from CD"
      window.aux1.document.write('<EMBED SRC="' + CDRoot + mybase +movie+  '" width="16" height="16" controller="false" autoplay="true" type="video/quicktime"></EMBED>')
    }
    else
    {
      window.status="Playing audio from course server"
      window.aux1.document.write('<EMBED SRC="' + base_url + "/" +  movie+ '" width="16" height="16" controller="false" autoplay="true" type="video/quicktime"></EMBED>')
    }
  }
  status_timer = setTimeout("clear_status()",10000);
}

//--------------------------------------------------------------------
function clear_status() {
  window.status = "";    
}

//--------------------------------------------------------------------
function bread_crumb() {
  // This function is only used in courses where XML_MENU is false.
  if (window.back_page)
  {
    b = (bread.length);
    punctuation_re = /\"|\'/g
    the_title = window.main.document.title.replace(punctuation_re,"");
    old_title = the_title;
    if (b == 0) {
      bread[b] = '<a href = "' + window.main.location + '"  onMouseOver="status_message(\'' + old_title + '\');return document.MM_returnValue" onMouseOut="status_message(no_message);return document.MM_returnValue" onclick = parent.close_back()><IMG SRC="bread_arrow.gif" WIDTH="22" HEIGHT="21" align="absmiddle" BORDER=0 ALT="' + the_title + '"><\/a>';        
    }
    else {
      bread[b] = '<a href = "' + window.main.location + '" onMouseOver="status_message(\'' + old_title + '\');return document.MM_returnValue" onMouseOut="status_message(no_message);return document.MM_returnValue" onclick = parent.clean_bread(' + b + ')><IMG SRC="bread_arrow.gif" WIDTH="22" HEIGHT="21" align="absmiddle" BORDER=0 ALT="' + the_title + '"><\/a>';    
    }
    if (USE_COOKIES) setCourseStateProp("bread", bread) // Save in cookie
    bread_list = "";
    bread_list += "Return links: ";
    for (i = 0; i < bread.length; i++) {
      bread_list += bread[i] + " | ";    
    }
    window.back_page.location.reload();
  } // end if (window.back_page)
}

//--------------------------------------------------------------------
function close_back() {
  // This function is only used in courses where XML_MENU is false.
  if (window.back_page)
  {
    bread_list = "";
    bread.length = 0;
    if (USE_COOKIES) setCourseStateProp("bread", bread) // Save in cookie
    window.back_page.location.reload();
  }
}

//--------------------------------------------------------------------
function clean_bread(crumb) {
  // This function is only used in courses where XML_MENU is false.
  if (window.back_page)
  {
    bread.length = crumb;
    if (USE_COOKIES) setCourseStateProp("bread", bread) // Save in cookie
    bread_list = "";
    bread_list += "Return links: ";
    for (i = 0; i < bread.length; i++) {
      bread_list += bread[i] + " | ";    
    }
    window.back_page.location.reload();
  }
}
//--------------------------------------------------------------------
function save_return_page()
{
  // Remembers the current page so that user can return to it by an appropriate click
  if (XML_MENU)
  {
    if (isMac && isIE) {
      window.return_page = window.ie_page1+","+window.ie_page2;
      window.menu.location.reload();
    }
    else {
      window.menu.return_button(window.main.document.location);
    }
  }
  else
    bread_crumb()
}
//--------------------------------------------------------------------
// finds the base url of a movie and pops it up into its own window
function playVideo2(movie,w,h){
  w_height = h + 70;
  w_width = w + 10;
  h = h + 10

  mybase = ""
  base_url = window.main.location.pathname.substring(0,window.main.location.pathname.lastIndexOf("/"))
  base = base_url.split("/")
  for (i = MEDIA_DEPTH; i < base.length;  i++) {
    mybase += base[i]  + "/"  
  }
  if (useCD == true) {
    m = window.open("","new_win","height=" + w_height + ",width=" + w_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no")
    m.document.write('<html><head><title>Movie Window</title></head><body bgcolor="white" marginwidth="0" marginheight="0" topmargin="0" leftmargin="0"><center><EMBED SRC="' + CDRoot + mybase + movie + '" autoplay=true width= "' + w + '" height= "' + h + '" controller=true type="video/quicktime"></embed></center><center><form><INPUT TYPE="button" name = "close" value="Close window" onclick = "self.close()"></form></center></body></html>')
  }
  else {
    base_url = folderURL(self)
    base_url = base_url.substring(0, base_url.length-1)
    movie_s = base_url + "/" + mybase + movie;
    m = window.open("","new_win","height=" + w_height + ",width=" + w_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no")
    m.document.write('<html><head><title>Movie Window</title></head><body bgcolor="white" marginwidth="0" marginheight="0" topmargin="0" leftmargin="0"><center><EMBED SRC="' + movie_s + '" autoplay=true width= "' + w + '" height= "' + h + '" controller=true type="video/quicktime"></embed></center><center><form><INPUT TYPE="button" name = "close" value="Close window" onclick = "self.close()"></form></center></body></html>')
  }
}

//--------------------------------------------------------------------
function popPlaySoundFrameset(movie)
{
  // Give PC Netscape the positive dimensions that it demands, and everything else 0 0
  if (navigator.appName.indexOf("Netscape") != -1 && navigator.platform.indexOf("Win") != -1) 
  {
    opener.window.aux1.document.write('<EMBED SRC="' + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
    opener.window.aux1.document.close();
  }
  else
  {
    opener.window.aux1.document.write('<EMBED SRC="' + movie + '" autoplay=true width=16 height=16 controller=false type="video/quicktime"></embed>')
    opener.window.aux1.document.close();
  }
}

//--------------------------------------------------------------------
function change_page(ext_page,url,ext_page2) {
  if (isMac && isIE) {
    window.ext_page_iemac = ext_page;
    window.ext_page_iemac2 = ext_page2;
    window.menu.location.reload();
  }
  else {
    window.menu.change_page(ext_page,url,ext_page2);   
  }
}

//--------------------------------------------------------------------
// plays audio files from popups in frameset
function playSoundWindow(movie,w,h){
  w_height = h + 70;
  w_width = w + 10;
  h = h + 10
  if (useCD == true) {
    window.status="Playing audio from CD"
    if (typeof audio_window == "undefined" || audio_window.closed) {
      audio_window = window.open("","new_win","height=" + w_height + ",width=" + w_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no")
      if (!audio_window.opener) audio_window.opener = window    
    }
    else {
     audio_window.focus()    
    }
    audio_window.document.write('<html><head><title>Movie Window</title></head><body bgcolor="white" marginwidth="5" leftmargin="5" marginheight="0" topmargin="0"><EMBED SRC="' + CDRoot + movie + '" autoplay=true width= "' + w + '" height= "' + h + '" controller=true type="video/quicktime"></embed><center><form><INPUT TYPE="button" name = "close" value="Close window" onclick = "self.close()"></form></center></body></html>')
    audio_window.document.close()
  }
  else {
    window.status="Playing audio from course server"
    base_url = folderURL(self)
    base_url = base_url.substring(0, base_url.length-1)
    movie_s = base_url + "/" + movie;

    if (typeof audio_window == "undefined" || audio_window.closed) {
      audio_window = window.open("","new_win","height=" + w_height + ",width=" + w_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no");
      if (!audio_window.opener) audio_window.opener = window    
    }
    else {
      audio_window.focus()    
    }
    audio_window.document.write('<html><head><base href="/"><title>Movie Window</title></head><body bgcolor="white" marginwidth="5" leftmargin="5" marginheight="0" topmargin="0"><EMBED SRC="' + movie_s + '" autoplay=true width= "' + w + '" height= "' + h + '" controller=true type="video/quicktime"></embed><center><form><INPUT TYPE="button" name = "close" value="Close window" onclick = "self.close()"></form></center></body></html>')
    audio_window.document.close()
  }
  status_timer = setTimeout("clear_status()",10000);
}
/* ======================== Miscellaneous Utility Functions ======================== */

function getURLfromLinkText(theDocument, theText)
{
  /* This function looks in a specified document to find a link whose displayed
     label matches the specified text.  The match is case-insensitive,
     and ignores leading and trailing spaces in the text.
     If a match is found, the function returns the URL associated with the link;
     otherwise, it returns a zero-length string.
     Parameters:
       theDocument - A reference to the document object which contains the link.
       theText     - A string representing the clickable text associated with the link.
  */

  var uText = trim(theText.toUpperCase())
  var theURL = ""
  var i
  for (i=0; i<theDocument.links.length; i++)
  {
    if (trim(getLinkText(theDocument.links[i]).toUpperCase()) == uText)
    {
      theURL = theDocument.links[i].href
      break
    }
  }
  return theURL
}
//-------------------------------------------------------------------
function trim(theString)
{
  /* Returns a version of theString which has all leading and trailing spaces removed */
  var i, lastNonspace
  var firstNonspace = -1
  for (i=0; i<theString.length; i++)
  {
    if (theString.charAt(i) != " ")
    {
      firstNonspace = i
      break
    }
  }
  if (firstNonspace == -1) return ""

  for (i=theString.length-1; i>=0; i--)
  {
    if (theString.charAt(i) != " ")
    {
      lastNonspace = i
      break
    }
  }
  return theString.substring(firstNonspace, lastNonspace+1)
}
//-------------------------------------------------------------------
function getLinkText(theLink)
{
  /* Returns the clickable text string (if any) associated with the
     the link object specified by theLink.  It does this by looking
     at either the "text" property or the "innerText" property of the
     link, depending on whether the Document Object Model is in use.
     (Generally, the DOM is in use for MSIE browsers and not for
     Netscape browsers.)
  */

  var theText
  if (typeof document.all == "undefined")
  {
    if (inSafari())
    {
    	//Safari uses DOM, but document.all isn't defined 
    	if (theLink.innerText == null) theText=""; else theText = theLink.innerText
    }
    else
    {
    	// doesn't use DOM (typical of Netscape)
    	if (theLink.text == null) theText = ""; else theText = theLink.text
    }
  }
  else
  {
    // does use DOM (typical of MSIE)
    theText = theLink.innerText
  }
  //Replace any non-breaking spaces (ASC(160)) with "normal" spaces.  Some browsers
  //do this automatically, others don't.
  theText = theText.replace(/\xA0/g, " ")
  return theText
}
//---------------------------------------------------------------------
function LoadPage(theWindow, theLocation)
{
  /* Use this function when you need to access the document object for some URL,
     and you need to know when the document has actually finished loading.
     Parameters:
       theWindow   - A window object reference which will act as the destination
                     for the page to be loaded.  This is often a reference to a hidden frame.
       theLocation - The URL for the page to be loaded.  This MUST be on the same server
                     where this script lives!  Built-in browser security features prevent
                     javascript from accessing info about documents on other servers.
     
     The function returns an object which initially has one property called 'loaded', which
     initially is set to false.  Therefore, the function should be called like this:

       myObject = LoadPage(windowObj, theURL)

     After making this call, the calling logic should periodically check the "loaded" property
     in the returned object, as follows:
     
       if (myObject.loaded) {...}

     This property will initially be false, and will be set to true when the document is finished
     loading.  WARNING:  You should use something like setTimeout to give javascript a "breather"
     in between consecutive tests of myObject.loaded.  Logic like the following may result in an infinite loop:
     
       while (!myObject.loaded) {} //DON'T USE AN EMPTY LOOP TO WAIT FOR myObject.loaded !

     At the same time that myObject.loaded gets set to true, another property called "doc" will be set to
     the document object for the document that got loaded (i.e. the document that corresponds to theURL).

     PLEASE NOTE:
     * The returned document object is NOT the same object as theWindow.document.  It is actually a document
       in a subframe of theWindow.
     * If you call LoadPage twice with the SAME window reference, the object returned in the first
       call will no longer be valid.

         myFirstObj = LoadPage(myWindow, firstURL)
         mySecondObj = LoadPage(myWindow, secondURL) // This invalidates myFirstObj !

       Therefore, you should either use a different window (or frame) in the second call, or
       wait until you're completely finished with myFirstObj before making the second call.

  */
  var content = ""
  if (inSafari())
  {
  	//We erase the contents of frame1
  	content = "<"+"html>\n"
  	content += "<"+"frameset rows='100%,*'>\n"
  	content += "  <"+"frame name='frame1'>\n"
	content += "  <"+"frame name='dummy'>\n" //A dummy frame because some browsers require at least 2
  	content += "<"+"/frameset>\n"
  	content += "<"+"/html>\n"
  	theWindow.document.write(content)
  	theWindow.document.close()
  }
  content = "<"+"HTML>\n"
  if (inSafari())
  	content += "<"+"frameset rows='100%,*'>\n"
  else
  	content += "<"+"frameset rows='100%,*' onLoad='theObject.doc=frame1.document;theObject.loaded=true'>\n"
  content += "  <"+"frame name='frame1' src=\"" + theLocation + "\">\n"
  content += "  <"+"frame name='dummy'>\n" //A dummy frame because some browsers require at least 2
  content += "<"+"/frameset>\n"
  content += "<"+"/html>\n"
  if (inSafari())
  {
  	theWindow.theObject = new Object()
  	theWindow.theObject.loaded = false
  	theWindow.document.write(content)
  	theWindow.document.close()
  	LOAD_PAGE_URL[LOAD_PAGE_WINDOW_INDEX] = theLocation
  	LOAD_PAGE_WINDOW[LOAD_PAGE_WINDOW_INDEX++] = theWindow
  	if (INTERVAL_ID == null)
  	{
  		LOAD_PAGE_TIME = 1000
  		INTERVAL_ID = setInterval("checkForPageLoaded()",LOAD_PAGE_TIME)
  	}
  }
  else
  {
  	theWindow.document.write(content)
  	theWindow.document.close()
  	theWindow.theObject = new Object()
  	theWindow.theObject.loaded = false
  }
  return theWindow.theObject
}
//-----------------------------------------------------------------------------------------------------------
function isLoaded(theWindow)
{
	//Returns true if the contents of theWindow.frame1 has loaded.  More precisely, it returns true when the
	//document object exists for theWindow.frame1
	
	var temp = theWindow.frame1.document  //This NEEDS to be here to bypass a Safari bug

	if (theWindow.frame1.document) return true//If the document object exists return true
	return false
}
//-----------------------------------------------------------------------------------------------------------
function checkForPageLoaded()
{//Looks through the LOAD_PAGE_WINDOW array.  For each window object, w, in the array, if it finds the associated page has
 //loaded, it:
 //		*deletes w from the array
 //		*decreases LOAD_PAGE_WINDOW_INDEX by one
 //		*Sets w.theObject.doc = w.frame1.document
 //		*Sets w.theObject.loaded = true
 var i,j,w,theResult
 LOAD_PAGE_TIME *= 2
 for (i=0;i<LOAD_PAGE_WINDOW_INDEX;i++)
 {
 	if (isLoaded(LOAD_PAGE_WINDOW[i]))
	{
		w = LOAD_PAGE_WINDOW[i]
		w.theObject.loaded = true
		w.theObject.doc = w.frame1.document
		for (j=i+1;j<LOAD_PAGE_WINDOW_INDEX;j++)
		{
			LOAD_PAGE_WINDOW[j-1] = LOAD_PAGE_WINDOW[j];
			LOAD_PAGE_URL[j-1] = LOAD_PAGE_WINDOW[j];
		}
		LOAD_PAGE_WINDOW_INDEX--
		if (!LOAD_PAGE_WINDOW_INDEX)
		{
			clearInterval(INTERVAL_ID)
			INTERVAL_ID = null
		}
 	}
 	else
 	{
 		//If the page didn't load, we give it twice as long and try again
 		clearInterval(INTERVAL_ID)
 		INTERVAL_ID = setInterval("checkForPageLoaded()",LOAD_PAGE_TIME)
  		var content = "<"+"html>\n"
  		content += "<"+"frameset rows='100%,*'>\n"
  		content += "  <"+"frame name='frame1' src='" + LOAD_PAGE_URL[i] + "'>\n"
		content += "  <"+"frame name='dummy'>\n" //A dummy frame because some browsers require at least 2
  		content += "<"+"/frameset>\n"
  		content += "<"+"/html>\n"
  		LOAD_PAGE_WINDOW[i].document.write(content)
  		LOAD_PAGE_WINDOW[i].document.close()
 	}
 }
 return true
}
//-----------------------------------------------------------------------------------------------------------
function playMedia(target, locn_ref, filename, attributes, serverOnly, closeDoc)
{
  /*------------------------------------------------------------------------------------------
  This function will load and/or "play" (or display) various kinds of media, including
  graphics images, audio clips and video clips.  Use this function to present media
  in any of the following situations:
  * When the media piece may reside on the course CD; or:
  * When a piece needs to be loaded into a different frame (e.g. a hidden frame).
  * When you want to avoid the Windows IE "ActiveX alert" for media in <object>, <embed> and <applet> elements.

  ----------------
  Input Parameters
  ----------------
  
  target
  ------
  This should be one of the following:

  * A window or frame object.  In this case, the media piece will be loaded into the indicated
    window or frame.  Specify "self" (without quotes) to load the media into the current window or frame.

  * A 2-element array.  The first element should be a reference to a window or frame object; the second
    element should be a string which is the id of a <div> element that's in the indicated window or frame.
    In this case, the media piece will be loaded into the <div> element.

  * null.  In this case, The function will open a new popup window and will load the piece
    there.  The popup window will include a "Close Window" button.  The dimensions of the
    window will be determined by looking at the "attributes" parameter.

  locn_ref
  --------
  This is used to help find the path to the media file.  It should be one of the following:
  * A 1- or 2-element array.  The first element should always be self (i.e. a reference to the
    window that called playMedia).  The 2nd element, if present, should be a string which is a
    relative path from the calling document to the folder that contains the media file.  If the
    2nd element is omitted, it's assumed that the media file is in the same folder as the calling
    document.  Examples:

        [self]                    <- Indicates the media file is in same folder as calling document
        [self, '../01abc/media/'] <- 2nd element indicates relative path from document to media folder

  * self (i.e. a reference to the window that called playMedia).  In this case, it's assumed
    that there is a folder called "media" at the same directory level as the calling document's
    location.  The function will look in that folder (or in the analogous CD folder) to find
    the media file.  Note that, unlike the previous case, the "self" identifier does not appear
    in an array here.  Specifying locn_ref=self is the same as specifying locn_ref=[self,'media/'].
    Example:

        self    <- Indicates there's a folder called "media" at same level as document.
                   This form of locn_ref is identical to [self,'media/']

  * A partial path string.  This should be a partial path from the course's ROOT folder to the
    folder that contains the media piece; e.g. "00common/media/".  It should NOT be a partial
    path from the calling document's location! (unless the calling document happens to reside
    in the course's root folder).
    Example:

        '00common/01abc/media/'  <- Path from COURSE ROOT folder to media folder

  filename
  --------
  The name of the media file.  Don't include any path information here.  However, it is permissible
  to include a querystring following the file's name, if the media piece requires querystring parameters.
  Examples:

        'spinning_globe.mov'
        'activity1.swf?xml=../support/a1.xml&fruit=banana'

  attributes (optional)
  ----------
  This parameter is interpreted differently for different types of media, according to the
  file's extension.  The following interpretations are currently defined:

     extension   attributes parameter
     ---------   --------------------
       .mov      Either null, or an object with some or all of the following optional properties:

                 height:     height of movie rectangle
                 width:      width of movie rectangle
                 autoplay:   true or false.  Default is true.
                 controller: true or false.  Default is true.

                 If one of height or width is specified, the other must be specified also.
                 Height/width *must* be specified if the <target> parameter is null (i.e., if the
                 target is a new popup).  If height/width are not specified, the movie is assumed
                 to be an audio clip.

      .jpeg,
      .jpg,
      .gif       Either null, or an object with some or all of the following properties:

                 height: height of the image rectangle
                 width:  width of the image rectangle
                 usemap: name of a map to be associated with the image

                 If one of height or width is specified, the other must be specified also.
                 If height & width are not specified, the image is drawn in its default size rectangle.
                 Height/width *must* be specified if the <target> parameter is null (i.e., if the
                 target is a new popup).

      .swf       Either null, or an object with some or all of the following optional properties:

                 height:     height of movie rectangle
                 width:      width of movie rectangle
                 autoplay:   true or false.  Default is true.
                 version:    a string that should be appended to the URL for the Flash download
                             site, indicating the version of the Flash plug-in to be downloaded
                             in case the user doesn't have it.  Example: "5,0,0,0".  If this
                             property is omitted, and the user doesn't have Flash, I think that
                             by default the latest available Flash version is downloaded.

                 If one of height or width is specified, the other must be specified also.
                 Height/width *must* be specified if the <target> parameter is null (i.e., if the
                 target is a new popup).

      .dcr       Either null, or an object with both of the following properties:

                 height: Height of the director piece's rectangle
                 width:  Width of the director piece's rectangle

      .class     Either null, or an object with both of the following properties:

                 height: Height of the director piece's rectangle
                 width:  Width of the director piece's rectangle

      .mp3       Either null, or an object one or both of the following optional properties:
                 
                 width:      width of movie controller (if any)
                 autoplay:   true or false.  Default is true.

                 A controller will be present if and only if width is specified.  If the
                 controller is present, its height will always be 16.  The width *must* be
                 specified if the target is null (i.e. the target is a new popup).

      .ggb       An object specifying at least a "width" and "height" property.

  The attributes parameter is ignored for file extensions other than those listed above.  Also,
  it is an error to set the target parameter to null unless the file's extension is in this list,
  and the attributes parameter is specified.

  The attributes parameter may also contain other properties besides those listed above.  In most
  such cases, playMedia will add the specified attributes to the <object>, <embed>, <param>, etc.
  tags as appropriate.  It is not necessary to specify "standard" attributes like "src", "codebase",
  etc., as playMedia determines these from the other parameters.  But if you need to specify special
  miscellaneous attributes (like "bgcolor", "title", etc.) you can add them to the attributes parameter.

  serverOnly (optional)
  ----------
  This is a boolean.  If true, the function assumes that the media file does NOT exist on the
  CD, and will look only on the server.  If it's false, the function assumes that the CD does
  contain the media file, and will look on the CD (if the user indicated they're using a CD).

  If serverOnly is omitted or is null, the function treats it as false.

  closeDoc (optional)
  --------
  This is a boolean.  It's significant only if the target parameter indicates a window or frame object.
  If closeDoc is true, the function closes the document in the target window (i.e., it
  calls target.document.close) after writing the HTML to present the media piece.  Otherwise,
  it leaves the document open.  Please note that closing the document is NOT the same as closing
  the window.  It just indicates that no other HTML statements will follow the stuff written by
  the playMedia function.  In general, you should set this to true if the target is different from
  the window or frame that called the function.

  Note:
  * If target is null (i.e. if the function opens a new popup window), the closeDoc parameter
    is ignored.  The function always closes the document in this case.
  * If target is a 2-element array (i.e., if the media is being presented in a "div" structure),
    the closeDoc parameter is ignored.  The function never closes the document in this case.

  If closeDoc is omitted or is null, and target is a window or frame object, the function behaves as follows:
  * If the target indicates the special "aux1" frame, the function closes the document.
  * If the target incicates any other window or frame, the function does not close the document.

  -------------
  Return Values
  -------------
  The function does not return any value.

  ----------------
  Global Variables
  ----------------
  The function does not alter any global variables.  It does read the following global variables:
  * useCD
  * CDRoot
  -------------------------------------------------------------------------------------------*/

  //===================================== DEBUGGING ===================================
  //alert("Rev 1.0\nplayMedia called for " + filename)
  //=================================== END DEBUGGING =================================

  var supported_types = new Array("gif", "jpeg", "jpg", "mov", "swf", "dcr", "class", "mp3", "ggb")
  var i, found, movie_width, movie_height, media_type, media_url, base, temp
  var html, window_width, window_height, autoplay_value, controller_value
  var picture_width, picture_height, flash_version, usemap_value, q_media_url
  var qstring, rex, matches, filename_proper, theDiv, divWindow, divID
  var q_char, path_rex, matches, q_base, q_filename, misc_attributes, archive

  var isNS  = (window.navigator.appName.indexOf("Netscape") >= 0);
  var isIE  = (window.navigator.appName.indexOf("Explorer") >= 0);
  var isWin  = (window.navigator.userAgent.indexOf("Win") >= 0); 
  var isMac  = (window.navigator.userAgent.indexOf("Mac") >= 0);

  // Initialize values for certain variables:
  var errMsg = ""
  var window_title = ""

  // 1. Trim leading/trailing whitespace from any string arguments
  // -------------------------------------------------------------
  for (i in arguments)
  {
    if (typeof arguments[i] == "string" && arguments[i] != "")
    {
      // Would've been nice to do this via replace() method, but could not discover an
      // appropriate regular expression that worked consistently on all browsers.
      temp = arguments[i]
      while (temp.length > 0 && temp.substring(0,1)==" ") temp = temp.substring(1) // leading spaces
      while (temp.length > 0 && temp.substring(temp.length-1)==" ") temp = temp.substring(0,temp.length-1) // trailing spaces
      arguments[i] = temp
    }
  }

  // 2. Make sure all parameters look reasonable
  // -------------------------------------------
  if (!(target == null || IsWindow(target) || IsArray(target)))
    errMsg = "The target parameter should be null, or a window object, or an array."
  else if (IsArray(target) && !(IsWindow(target[0]) && typeof target[1]=="string"))
    errMsg = "When target parameter is an array, its 1st element should be a window reference and its 2nd element should be a string."
  else if (!(typeof locn_ref == "string" || IsWindow(locn_ref) || IsArray(locn_ref)))
    errMsg = "The locn_ref parameter should be a string, or a window object, or an array."
  else if (IsArray(locn_ref) && !IsWindow(locn_ref[0]))
    errMsg = "The first element in the locn_ref array should be a window reference (self)."
  else if (IsArray(locn_ref) && locn_ref.length > 1 && typeof locn_ref[1] != "string")
    errMsg = "The second element in the locn_ref array should be a string."
  else if (typeof locn_ref == "string" && (locn_ref.indexOf("/")==0 || locn_ref.indexOf("..")==0))
    errMsg = "\"" + locn_ref + "\" is an invalid locn_ref string.  Please specify a path relative to the course root directory."
  else if (typeof filename != "string")
    errMsg = "The filename parameter should be a string."
  else if (!(serverOnly==null || typeof serverOnly == "boolean"))
    errMsg = "The serverOnly parameter should be true or false."
  else if (!(closeDoc==null || typeof closeDoc == "boolean"))
    errMsg = "The closeDoc parameter should be true or false."

  // 3. Check the media type, based on file extension
  // ------------------------------------------------
  if (errMsg=="")
  {
    rex = /([^?]*\.)(\w+)(\?.*)?$/
    matches = rex.exec(filename)
    if (matches==null)
      errMsg = "Can't tell what type of media file this is."
    else
    {
      media_type = matches[2].toLowerCase()
      qstring = matches[3]; if (qstring==null) qstring=""
      filename_proper = matches[1] + matches[2] //excludes querystring, if any
      if (filename_proper.indexOf("/") > -1) errMsg = "The filename parameter should not include any path information"
    }
  }
  if (errMsg=="")
  {
    found = false
    for (i in supported_types)
    {
      if (media_type == supported_types[i]) found = true
    }
    if (found)
    {
      // Check whether the attributes parameter is OK
      switch (media_type)
      {
        case "mov":
          // Initialize default values
          autoplay_value = "true"
          controller_value = "true"
          movie_width = null
          movie_height = null

          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height

            if (typeof attributes.autoplay=="boolean")
              autoplay_value = attributes.autoplay.toString()
            else if (typeof attributes.autoplay=="string")
              autoplay_value = attributes.autoplay

            if (typeof attributes.controller=="boolean")
              controller_value = attributes.controller.toString()
            else if (typeof attributes.controller=="string")
              controller_value = attributes.controller

            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .mov files, the attributes parameter should either be null or an object."
          break;          
        case "gif": case "jpeg": case "jpg":
          // Initialize default values
          picture_width = null
          picture_height = null
          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            usemap_value = attributes.usemap
            if (usemap_value != null)
            {
              usemap_value = String(usemap_value)
              if (usemap_value.charAt(0) != "#") usemap_value = "#" + usemap_value
            }
            picture_width = attributes.width
            picture_height = attributes.height
            if ((picture_height==null) != (picture_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (picture_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For image files, the attributes parameter should either be null or an object."
          break;
        case "swf":
          // Initialize default values
          autoplay_value = "true"
          flash_version = null
          movie_width = null
          movie_height = null

          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height

            if (typeof attributes.autoplay=="boolean")
              autoplay_value = attributes.autoplay.toString()
            else if (typeof attributes.autoplay=="string")
              autoplay_value = attributes.autoplay

            if (attributes.version) flash_version = attributes.version

            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .swf files, the attributes parameter should either be null or an object."
          break;
        case "dcr":
          // Initialize default values
          movie_width = null
          movie_height = null

          if (attributes==null)
            {} //(keep default values)
          else if (typeof attributes=="object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height
            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .dcr files, the attributes parameter should either be null or an object."
          break;
        case "class":
          // Initialize default values
          movie_width = null
          movie_height = null

          if (attributes==null)
            {} //(keep default values)
          else if (typeof attributes=="object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height
            archive = attributes.archive
            if ((movie_height==null) != (movie_width==null))
              errMsg = "Caller specified one of the height/width attributes, but not the other."
            else if (movie_height==null && target==null)
              errMsg = "Couldn't open a new popup window, because no height/width information was given."
          }
          else
            errMsg = "For .class files, the attributes parameter should either be null or an object."
          break;
        case "mp3":
          // Initialize default values
          autoplay_value = "true"
          controller_value = "false"
          movie_width = null
          movie_height = null

          if (attributes == null)
            {} //(keep default values)
          else if (typeof attributes == "object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            if (movie_width != null) {
              controller_value = "true"
              movie_height = 16
            }

            if (typeof attributes.autoplay=="boolean")
              autoplay_value = attributes.autoplay.toString()
            else if (typeof attributes.autoplay=="string")
              autoplay_value = attributes.autoplay

            if (movie_width==null && target==null)
              errMsg = "Couldn't open a new popup window, because no width information was given."
          }
          else
            errMsg = "For .mp3 files, the attributes parameter should either be null or an object."
          break;          
        case "ggb":
          if (attributes==null)
            errMsg = "For .ggb files, the attributes parameter should be an object specifying (at least) 'width' and 'height'."
          else if (typeof attributes=="object")
          {
            attributes = squashProps(attributes)
            movie_width = attributes.width
            movie_height = attributes.height
            archive = attributes.archive || "http://www.geogebra.org/webstart/geogebra.jar"
            if ((movie_height==null) || (movie_width==null))
              errMsg = "For .ggb files, the attributes parameter should specify (at least) 'width' and 'height'."
          }
          else
            errMsg = "For .ggb files, the attributes parameter should be an object specifying (at least) 'width' and 'height'."
          break;
      } // end switch
    }
    else
      errMsg = "The function does not support files of this type."
  } // end if (errMsg=="")

  if (errMsg != "")
  {
    playMedia_error(filename, errMsg)
    return
  }

  // 4. Use default values for any undefined parameters
  // --------------------------------------------------
  if (serverOnly==null) serverOnly = false
  if (target==null) closeDoc = true
  if (closeDoc==null) closeDoc = (target==window.aux1)

  // 5. Get the URL for the media file
  // ---------------------------------
  if (typeof locn_ref=="string") // path relative to course root
    var server_media_folder = URLglue(folderURL(self), locn_ref)
  else if (IsWindow(locn_ref)) // self (window object of caller)
    server_media_folder = folderURL(locn_ref) + "media/"
  else if (locn_ref.length == 1) // [self] (window object of caller, in an array)
    server_media_folder = folderURL(locn_ref[0])
  else // [self, path_relative_to_document]
    server_media_folder = URLglue(folderURL(locn_ref[0]), locn_ref[1])

  if (serverOnly || !useCD)
  {
    // Get the file from the server
    base = server_media_folder
  }
  else
  {
    // Get the file from the CD
    var server_course_root = folderURL(self)
    var media_target_course = targetCourse(server_media_folder)
    if (media_target_course.toLowerCase() != STARTUP_PARMS.course.toLowerCase())
    {
      // The media is in a different course than the "home" course, which means it's not in the CDRoot folder.
      // Instead, it's in a "sister" folder of CDRoot, which should have the same name as media_target_course.
      var cyclops_folder_url = server_course_root.substr(0, server_course_root.length-STARTUP_PARMS.course.length-1)
      if (server_media_folder.substr(0,cyclops_folder_url.length) != cyclops_folder_url)
      {
        playMedia_error(filename, "Can't figure out how to translate media path (" + server_media_folder + ") into a path on the CD.")
        return
      }
      else
        base = URLglue(CDRoot, "../") + server_media_folder.substr(cyclops_folder_url.length)
    }
    else
    {
      // The media is in the CDRoot folder.
      // Chop the beginning off of server_media_folder, leaving a relative path:
      var rel_path = server_media_folder.substring(server_course_root.length)
      base = CDRoot + rel_path
    }
  }
  if (base.substring(base.length-1) != "/") base += "/"
  if (navigator.userAgent.indexOf("Safari") != -1  && media_type=="mov")
    media_url = base + filename_proper + qstring // "unescaped" URL won't work in Safari QuickTime plugin
  else
    media_url = unescape(base + filename_proper) + qstring // "escaped" URL won't work in some plugins (e.g. Win IE Flash)

  if (media_url.indexOf("'")==-1)
    q_media_url = "'" + media_url + "'"
  else if (media_url.indexOf('"')==-1)
    q_media_url = '"' + media_url + '"'
  else
  {
    // Yikes! This URL contains BOTH kinds of quotes!
    playMedia_error(filename, "Can't handle the quotes in this URL:\n" + media_url)
    return
  }
  //===================================== DEBUGGING ==============================
  // alert("Gonna play:\n" + media_url)
  //=================================== END DEBUGGING ============================

  // 6. Generate the HTML to write to the target
  // -------------------------------------------
  switch (media_type)
  {
    case "gif": case "jpeg": case "jpg":
      html = "<"+"img src=" + q_media_url
      if (picture_width != null) html += " width='" + picture_width + "' height='" + picture_height + "'"
      if (usemap_value != null) html += " usemap='" + usemap_value + "'"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","usemap")))!=null) html+=buildAttrString(misc_attributes)
      html += ">\n"
      if (target==null) //(i.e., if new popup)
      {
        window_width = picture_width + 10
        window_height = picture_height + 70
      }
      break
    case "mov": case "mp3":
      if (target == window.aux1)
      {
        // Plays in hidden frame
        autoplay_value = "true"
        controller_value = "false"
      }
      if (movie_width==null)
      {
        // Assume it's an audio clip.  Use Apple-recommended width/height:
        movie_width = 16
        movie_height = 16
      }
      else if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      
      // The following is according to the Apple Quicktime website:
      // http://developer.apple.com/quicktime/compatibility.html
      html = "<"+"OBJECT CLASSID='clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B' WIDTH='" + movie_width +"' HEIGHT='" + movie_height + "' CODEBASE='http://www.apple.com/qtactivex/qtplugin.cab'>\n"
      html += "<"+"PARAM name='SRC' VALUE=" + q_media_url + ">\n"
      html += "<"+"PARAM name='AUTOPLAY' VALUE='" + autoplay_value + "'>\n"
      html += "<"+"PARAM name='CONTROLLER' VALUE='" + controller_value + "'>\n"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","autoplay","controller","classid","codebase","type","pluginspage")))!=null)
        html += buildParamTags(misc_attributes)
      // Note: Use "video/quicktime" as the type, even for mp3 audio.  Other values won't work in Firefox.
      html += "<"+"EMBED SRC=" + q_media_url + " WIDTH='" + movie_width + "' HEIGHT='" + movie_height +"' AUTOPLAY='" + autoplay_value + "' CONTROLLER='" + controller_value + "' type='video/quicktime' PLUGINSPAGE='http://www.apple.com/quicktime/download/'" + buildAttrString(misc_attributes) + ">\n"
      html += "<"+"/EMBED>\n"
      html += "<"+"/OBJECT>\n"
      break
    case "swf":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      html = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab"
      if (flash_version) html += "#version=" + escape(flash_version)
      html += "'"
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      html += ">\n"
      html += "<"+"PARAM name='movie' VALUE=" + q_media_url + ">\n"
      html += "<"+"PARAM name='SRC' VALUE=" + q_media_url + ">\n"
      html += "<"+"PARAM name='PLAY' VALUE='" + autoplay_value + "'>\n"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","movie","play","autoplay","classid","codebase","version","type","pluginspage")))!=null)
        html += buildParamTags(misc_attributes)
      html += "<"+"EMBED SRC=" + q_media_url + " QUALITY='high' pluginspage='http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash' type='application/x-shockwave-flash' PLAY='" + autoplay_value + "'"
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if (misc_attributes) html += buildAttrString(misc_attributes)
      html += ">\n"
      html += "<"+"/EMBED>\n"
      html += "<"+"/OBJECT>\n"
      break
    case "dcr":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      html = "<"+"EMBED SRC=" + q_media_url + " type='application/x-director'"
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if ((misc_attributes=stripProps(attributes,new Array("src","width","height","type")))!=null) html += buildAttrString(misc_attributes)
      html += "><"+"/EMBED>\n"
      break
    case "class":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "Movie Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      q_char = q_media_url.substr(0,1) //(single or double quote)
      path_rex = /^(.*)\/([^\/]+)$/
      matches = path_rex.exec(q_media_url)
      q_base = matches[1] + q_char
      q_filename = q_char + matches[2]
      // The <applet> tag is deprecated in favor of <object>.  However, current versions of IE and Safari
      // won't do applets with <object>; so use the deprecated form instead!
      html = "<"+"applet codebase=" + q_base + " code=" + q_filename
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if (archive) html+= " archive='" + archive + "'"
      if ((misc_attributes=stripProps(attributes,new Array("codebase","code","width","height","type","archive")))!=null) html += buildAttrString(misc_attributes)
      html += ">\n"
      if (misc_attributes) html += buildParamTags(misc_attributes)
      html += "You need a Java-enabled browser to see this applet.\n"
      html += "<"+"/applet>\n"
      break;
    case "ggb":
      if (target==null) //(i.e., if new popup)
      {
        window_title = "GeoGebra Window"
        window_width = movie_width + 10
        window_height = movie_height + 70
      }
      q_char = q_media_url.substr(0,1) //(single or double quote)
      path_rex = /^(.*)\/([^\/]+)$/
      matches = path_rex.exec(q_media_url)
      q_base = matches[1] + q_char
      q_filename = q_char + matches[2]
      // The <applet> tag is deprecated in favor of <object>.  However, current versions of IE and Safari
      // won't do applets with <object>; so use the deprecated form instead!
      html = "<"+"applet name='ggbApplet' code='geogebra.GeoGebraApplet' codebase=" + q_base
      if (movie_width) html += " WIDTH='" + movie_width + "'"
      if (movie_height) html += " HEIGHT='" + movie_height + "'"
      if (archive) html+= " archive='" + archive + "'"
      if ((misc_attributes=stripProps(attributes,new Array("codebase","code","width","height","type","archive","filename")))!=null) html += buildAttrString(misc_attributes)
      html += ">\n"
      html += "<param name='filename' value=" + q_filename + ">\n"
      if (misc_attributes) html += buildParamTags(misc_attributes,{framePossible:false, showResetIcon:true, enableRightClick:false, showMenuBar:false, showToolBar:false, showToolBarHelp:false, showAlgebraInput:false})
      html += "Sorry, the GeoGebra Applet could not be started.  Please make sure that Java 1.4.2 (or later) is installed and active in your browser (<"+"a href=\"http://java.sun.com/getjava\" target=\"_blank\">Click here to install Java now</a>)\n"
      html += "<"+"/applet>\n"
      break;
    default:
      playMedia_error(filename, "Internal error: this media type IS supported, but no handler was found!")
      return
  }

  // 7. Open a popup window if necessary
  // -----------------------------------
  if (target==null)
  {
    // Redefine the "target" variable
    var features = "height=" + window_height + ",width=" + window_width + ",status=yes,scrollbars=yes,resizable=yes,toolbar=no"
    target = window.open("", "", features)

    // Add to the "html" variable
    var header = "<"+"html>\n"
    header += "<"+"head><"+"title>" + window_title + "<"+"/title><"+"/head>\n"
    header += "<"+"body bgcolor='white' marginwidth='0' marginheight='0' topmargin='0' leftmargin='0'>\n"
    header += "<"+"center>\n"
    var footer = "<"+"form>\n"
    footer += "<"+"INPUT TYPE='button' name = 'close' value='Close window' onclick = 'self.close()'>\n"
    footer += "<"+"/form>\n"
    footer += "<"+"/center>\n"
    footer += "<"+"/body>\n"
    footer += "<"+"/html>\n"
    html = header + html + footer
  }
  else if (closeDoc)
  {
    // Surround with HTML tags for good measure
    html = "<"+"html>\n" + html + "<"+"/html>\n"
  }

  // 8. Write to the target
  // ----------------------
  if (IsArray(target))
  {
    // target[0] is a window reference; target[1] is the ID of a <div> in that window.
    divWindow = target[0]; divID = target[1]
    if (document.layers)
    {
       // This browser supports layers; treat the <div> like a window object:
      theDiv = divWindow.document.layers[divID]
      if (theDiv)
      {
        theDiv.document.open();
        theDiv.document.write(html);
        theDiv.document.close();
      }
    }
    else if (document.all)
    {
      // This browser supports an MSIE-like DOM
      theDiv = divWindow.document.all[divID]
      if (theDiv) theDiv.innerHTML = html
    }
    else if (document.getElementById)
    {
      // This browser supports getElementByID
      theDiv = divWindow.document.getElementById(divID)
      if (theDiv) theDiv.innerHTML = html
    }
    if (theDiv==null)
    {
      playMedia_error(filename, "Could not find a <div> with ID '" + divID + "' inside the specified window.")
      return
    }
  }
  else
  {
    // target is a window reference (possibly to newly-opened popup)
    target.document.write(html)
    if (closeDoc) target.document.close()
  }
  return
}
//------------------------------------------------------------------------------------------------
function IsWindow(item)
{
  // Performs a rough test to determine whether the passed item is a reference to a window object.
  // If item is a window object, the function returns true.
  // It item is not a window object, the function *almost* always returns false, but it's possible
  // to fool it by passing any object which has a "window" property equal to the object itself.
  if (item==null)
    return false
  else
    return (item.window==item) // This syntax works, even if item's not an object
}
//------------------------------------------------------------------------------------------------
function playMedia_error(filename, errMsg)
{
  var msg = "The PlayMedia function was unable to process the file, \"" + filename + "\"."
  msg += "\nReason: " + errMsg
  alert(msg)
}
//------------------------------------------------------------------------------------------------
function squashProps(theObject)
{
  // Returns a copy of theObject in which all the property names have been forced to lower case
  var newObject = new Object()
  for (var prop in theObject) newObject[prop.toLowerCase()] = theObject[prop]
  return newObject
}
//------------------------------------------------------------------------------------------------
function playAudio(locn_ref, filename)
{
  // Loads and plays an audio file in the "aux1" frame
  playMedia(window.aux1, locn_ref, filename)
}
//------------------------------------------------------------------------------------------------
function playServerAudio(locn_ref, filename)
{
  // Loads and plays a "server only" audio file in the "aux1" frame
  playMedia(window.aux1, locn_ref, filename, null, true)
}
//------------------------------------------------------------------------------------------------
function stopAudio()
{
  // Loads dummy contents into the "aux1" frame, thus stopping any sound that may be playing there.
  self.aux1.document.write("<html></html>")
  self.aux1.document.close()
}
//------------------------------------------------------------------------------------------------
function URLglue(base, rel_path)
{
  /*
    Builds and returns a URL to the resource indicated by base and rel_path.  Parameters:
      base     - A relative or absolute URL that indicates a folder.  A trailing "/" is optional.
                 May also be blank, in which case the returned URL is the same as rel_path.
      rel_path - A relative URL.  May also be blank, in which case the returned URL is the same
                 as base (possibly with a trailing "/" appended).
    If there are no errors in the parameters, the function returns a URL for rel_path which assumes
    rel_path is relative to base.  If base is an absolute URL, the returned URL will also be absolute.
    If an error is found in the parameters (for example, if rel_path can't be interpreted as relative
    to base), an alert is displayed, and the function returns null.

    The function assumes there are no leading or trailing spaces in base or in rel_path.
  */
  var error
  var old_base = base
  var old_rel_path = rel_path
  var isRoot = base.charAt(0)=="/" || base.indexOf("://") != -1
  if (base.charAt(base.length-1) == "/") base = base.substring(0, base.length-1) // Strip trailing "/"
  if (rel_path.charAt(0)=="/" || rel_path.indexOf("://") != -1) error = true // rel_path is an absolute URL
  var done = ((rel_path.substring(0,3) != "../" || base.length == 0) && !error)
  while (!done)
  {
    rel_path = rel_path.substring(3) // Chop off first 3 characters
    base = base.substring(0, base.lastIndexOf("/")) // Chop trailing directory and the preceding "/"
    if (base.length == 0)
    {
      done = true
      if (isRoot && rel_path.substring(0,3) == "../") error = true
    }
    else if (/(\w|\.\.|:)$/.test(base)==false) // if base does not end with letter/digit/underscore/double-dot/colon
    {
      done = true
      error = true
    }
    else if (rel_path.substring(0,3) != "../")
      done = true
  }
  if (error)
  {
    alert("Invalid arguments passed to URLglue:\n" + old_base + "\n" + old_rel_path)
    return null
  }
  else
  {
    if (base.length > 0 || isRoot) base += "/" //Put the slash back on
    return base + rel_path
  }
}
//--------------------------------------------------------------------
//opens last page accessed (in previous session) when the last-page toolbar button is pressed
function get_last_page(text) {
  //pulls last page from Electric Blackboard

  if (XML_MENU) show_content("menu_index"); else show_content("loc")

  if ((v_name != "") && (typeof v_name != "undefined")) {
    //gives user a choice of whether to open last page; also gives above function time to get link before page is changed
    if (window.confirm(text)) {
      if (XML_MENU) {
        var page1 = v_name;
        show_content("page_index");
        var page2 = v_name;
        change_page(page1," ",page2);
        //parent.main.location = v_name;
      }
      else
        window.main.location = v_name
    }
  }   
  else {
    alert("Error: Cannot load the last lesson page opened.");    
  }
}
//--------------------------------------------------------------------
// Adds new data to the global variable "notebook_content", which will eventually
// be written to the Electric Blackboard.  This data is usually, but not necessarily,
// the content of a notebook assignment.
function save_contents(delimiter,contents) {
  var added, result, parsed_result
  if (SAVE_DATA)
  {
    var is_a_notebook = (!(delimiter=="menu_index" || delimiter=="page_index" || delimiter=="loc" || delimiter=="loc2"))
    if (XML_MENU)
    {
      if (delimiter=="menu_index") {
        window.ie_page1 = contents;
        if (window.main.document.title == "Page Not Found") contents = ""
      }
      if (delimiter=="page_index") window.ie_page2 = contents;
    }
  
    if (delimiter=="loc" || delimiter=="loc2") {
      if (window.main.document.title == "Page Not Found") contents = ""
    }

    if (notebook_content == "") { 
      notebook_content = delimiter+"&y#&"+contents+"&z&&" 
      added = true
    }
    else {  
      added = false
      result = notebook_content.split(/&z&&/)
      for (var i=0;i<result.length;i++) {
        parsed_result = result[i].split(/&y#&/)
        if (parsed_result[0] == delimiter) {
          result[i] = delimiter+"&y#&"+contents+"&z&&"
          added = true
        }
        notebook_content = result.join("&z&&");
      }
    }
    if (added == false) {
      result[i] = delimiter+"&y#&"+contents+"&z&&"
      notebook_content = result.join("&z&&");
    }

    if (is_a_notebook)
    {
      if (reminder_open == false || reminder.closed) {
        reminder()
        reminder_open = true;
      }
      else {
        reminder.content = notebook_content
      }
    }

    if (!XML_MENU)
    {
      if (isMac && isNS) {
        if (show_notice == true && window.main.action != "mail" && delimiter != "loc") {
          var notice_timer = setTimeout("change_notice()",1000);   
        }
      }
      show_notice = true;
    }

    if (USE_COOKIES) setCourseStateProp("notebook_content", notebook_content) // Save it in a cookie
  }
  else //(SAVE_DATA is false)
    alert("The save_contents function was called in error.  No data will be saved.")
}

//--------------------------------------------------------------------
function change_notice() {
  // This function is used only in courses where XML_MENU is false.
  window.main.location = "save_notice.htm";    
}

//--------------------------------------------------------------------
function send_to_reminder() {
  // This function is used only in courses where SAVE_DATA is true.
  reminder.content = notebook_content    
}

//--------------------------------------------------------------------
//parses notebook array and returns with variables
function show_content(variable_name) {
  var search_string = notebook_content;
  var result = search_string.split(/&z&&/)
  for (var i=0;i<result.length;i++) {
    var parsed_result = result[i].split(/&y#&/)
    if (parsed_result[0] == variable_name) {
      v_name = parsed_result[1]
      return v_name
    }
    else {
      v_name = ""
    }
  }
}
//--------------------------------------------------------------------
//opens notebook reminder window when notebook is saved for first time each frameset session.
// This function is only used if SAVE_DATA is true.
function reminder() {
  var base_url, reminder_link
  if (m_notebook == true) {
    reminder_link = "reminder.htm"
    m_notebook = false;
  }
  else {  
    base_url = folderURL(self)
    base_url = base_url.substring(0, base_url.length-1)
    reminder_link = base_url + "/reminder.htm"
  }
  reminder = window.open(reminder_link,"remind_win","height=290,width=300,scrollbars=no,resizable=no,toolbar=no");
  var reminder_send_timer = setTimeout("send_to_reminder()",4000);
}

//--------------------------------------------------------------------
//closes notebook reminder window from toolbar's frameset "Close" button
// This function is only used if SAVE_DATA is true.
function close_popup() {
  if (reminder_open == true)
  {
    window.reminder.close(); 
    reminder_open = false
  }
}

//--------------------------------------------------------------------
// reads quiz array and puts quiz text in main window
// This function is only used if FRAMESET_CONTROLS is true.
function get_quiz(link) {
  if (window.confirm("Are you sure that you want to take this quiz now?")) {
    save_return_page()
    window.main.location = window.quiz_list[link];
  }
}

//--------------------------------------------------------------------
//called from assessments window
// This function is only used if FRAMESET_CONTROLS is true.
function get_quiz2(link) {
  if (window.confirm("Are you sure that you want to take this quiz now?")) {
    window.main.location = window.quiz_list[link];
  }
}

//--------------------------------------------------------------------
// reads notebook array and puts notebook text in main window
// This function is only used if FRAMESET_CONTROLS is true.
function get_notebook(link) {
  save_return_page()
  window.main.location = window.notebook_list[link];
}

//--------------------------------------------------------------------
// called from assessments window
// This function is only used if FRAMESET_CONTROLS is true.
function get_notebook2(link) {
  window.main.location = window.notebook_list[link];
}

//--------------------------------------------------------------------
//opens assessment from toolbar assessments button
// This function is only used if FRAMESET_CONTROLS is true.
function get_assessments() {
  if (!XML_MENU) close_back()
  save_return_page()
  window.main.location = "../_CourseCommon/assess.htm"
}

//--------------------------------------------------------------------
// This function is only used if FRAMESET_CONTROLS is true.
function get_notebook_link(url) {
  save_return_page()
  var base_url = folderURL(self)
  base_url = base_url.substring(0, base_url.length-1)
  var notebook_link = base_url + "/" + url;
  window.main.location = notebook_link
}

//--------------------------------------------------------------------
//popup from notebook page loaded in Blackboard
// This function is only used if FRAMESET_CONTROLS is true.
function get_notebook_link2(url,settings) {
  var base_url = folderURL(self)
  base_url = base_url.substring(0, base_url.length-1)
  var popup_link = base_url + "/" + url;
  var popup_win = window.open(popup_link,"new_win",settings);    
}

//--------------------------------------------------------------------
//calls notebook within frameset from link in frameset
// This function is only used if FRAMESET_CONTROLS is true.
function get_notebook_frameset(url) {
  save_return_page()
  window.main.location = url; 
}

//--------------------------------------------------------------------
function get_lms_string(category)
{
  /*
    This function returns a string whose content is specific to the current LMS.
    This is intended for use within the course content where some displayed text
    needs to be different from one LMS to another.

    Parameters:
      category  - A number which indicates the "category" of the desired string.
                  Currently defined categories are:
                    0  - A default "place-holder" string.  This index value can be used during
                         course development, but should never be used in "production" courses.
                    1  - The name of the LMS (e.g., "Blackboard")
                    2  - A location for discussion forums (e.g. "discussion group")
                    3  - A location for accessing Quizzes (e.g. "Assignments area")
                    4  - A location for sending assignments to instructor (e.g. "Dropbox")

    If the given category does not apply to the current LMS, the function returns a blank string.
  */

  //-------- Add new categories here as appropriate -------------
  var cat_placeholder     = 0
  var cat_lms_name        = 1
  var cat_discussion      = 2
  var cat_quiz_area       = 3
  var cat_teacher_dropbox = 4
  var cat_final_quiz_area = 5
  //-------------------------------------------------------------
  if (typeof LMS_PLATFORM != "object")
  {
    LMS_PLATFORM = new Object()
    LMS_PLATFORM.name = ""
    LMS_PLATFORM.version = "0"
  }
  var lms_specific_string = ""
  //------------------ Add new (lower case!) LMS names here as appropriate -----------------------
  /*
    Note: if a particular category doesn't apply to a particular lms platform,
    just omit it from the corresponding "case" block.
  */
  switch (LMS_PLATFORM.name.toLowerCase())
  {
    case "blackboard":
      if (category == cat_placeholder)
        lms_specific_string = "BLACKBOARD PLACEHOLDER STRING"
      else if (category == cat_lms_name)
        lms_specific_string = "Blackboard"
      else if (category == cat_discussion)
        lms_specific_string = "Discussion Board"
      else if (category == cat_quiz_area)
        lms_specific_string = "Assignments area"
      else if (category == cat_teacher_dropbox)
        lms_specific_string = "Digital Drop Box"
      else if (category == cat_final_quiz_area)
        lms_specific_string = "Final Assessment module"
      break;
    case "ecollege":
      if (category == cat_placeholder)
        lms_specific_string = "E-COLLEGE PLACEHOLDER STRING"
      else if (category == cat_lms_name)
        lms_specific_string = "eCollege"
      else if (category == cat_discussion)
        lms_specific_string = "Unit module"
      else if (category == cat_quiz_area)
        lms_specific_string = "Unit Menu"
      else if (category == cat_teacher_dropbox)
        lms_specific_string = "Dropbox"
      else if (category == cat_final_quiz_area)
        lms_specific_string = "Finals module"
      break;
    case "ucompass":
      if (category == cat_placeholder)
        lms_specific_string = "UCOMPASS PLACEHOLDER STRING"
      else if (category == cat_lms_name)
        lms_specific_string = "ucompass"
      else if (category == cat_discussion)
        lms_specific_string = "Discussion area"
      else if (category == cat_quiz_area)
        lms_specific_string = "Quizzes area"
      else if (category == cat_final_quiz_area)
        lms_specific_string = "Quizzes area"
      break;
    case "webct":
      if (category == cat_placeholder)
        lms_specific_string = "WEBCT PLACEHOLDER STRING"
      else if (category == cat_lms_name)
        lms_specific_string = "WebCT"
      else if (category == cat_discussion)
        lms_specific_string = "Discussion Board"
      else if (category == cat_quiz_area)
        lms_specific_string = "Assignments area"
      else if (category == cat_teacher_dropbox)
        lms_specific_string = "Mailbox"
      else if (category == cat_final_quiz_area)
        lms_specific_string = "Assignments area"
      break;
    case "learndotcom":
      if (category == cat_placeholder)
        lms_specific_string = "LEARN.COM PLACEHOLDER STRING"
      else if (category == cat_lms_name)
        lms_specific_string = "Learn.com"
      else if (category == cat_discussion)
        lms_specific_string = "discussion activity in the LearnCenter Forum"
      else if (category == cat_quiz_area)
        lms_specific_string = "Unit Menu"
      else if (category == cat_final_quiz_area)
        lms_specific_string = "Final Menu"
      break;
    default:
      if (category == cat_placeholder)
        lms_specific_string = "PLACEHOLDER STRING -- UNKNOWN LMS"
      else if (category == cat_lms_name)
        lms_specific_string = "UNKNOWN LMS"
      else if (category == cat_discussion)
        lms_specific_string = "Unit module"
      else if (category == cat_quiz_area)
        lms_specific_string = "Unit module"
      else if (category == cat_teacher_dropbox)
        lms_specific_string = "instructions given by your teacher"
      else if (category == cat_final_quiz_area)
        lms_specific_string = "Final module"
      break;
  }
  //----------------------------------------------------------------------------------------------
  return lms_specific_string
}
//---------------------------------------------------------------------------------
function write_lms_string(windowRef, category, prefix, suffix, defaultString)
{
  /*
    Writes an LMS-specific string using document.write.

    Parameters:
      windowRef     - A reference to the window containin the document we should write to.
                      Almost always, this should be self.
      category      - A number which indicates the "category" of the desired string.  See
                      function get_lms_string for a list of defined categories
      prefix        - (optional).  A string which should precede the LMS-specific part of the string.
      suffix        - (optional).  A string which should follow the LMS-specific part of the string.
      defaultString - (optional).  A string which should be displayed in case the indicated category
                      is not applicable to the current LMS.

    Some category values may not be applicable to every LMS.  If the category is applicable to the
    current LMS, the function writes a concatenation of the prefix (if any), plus the LMS-specific
    string, plus the suffix (if any).

    If the category is not applicable to the current LMS, the function writes the defaultString (if any).
    If the defaultString is omitted, the function does nothing.  In either case, the prefix and suffix are ignored.
  */
  var i
  var whitespace = /\s/
  var allwhite = /^\s*$/
  var trailingwhite = /(^.*[^\s])\s*$/
  var leadingwhite = /^\s*([^\s].*)$/
  if (prefix==null || allwhite.test(prefix))
    prefix = ""
  else
  {
  	if (inSafari())
  	{
  		for (i=prefix.length - 1;i>=0;i--) if (!whitespace.test(prefix.charAt(i))) break;
  		prefix = prefix.substring(0,i+1) + " "
  	}
  	else
  	{
    	trailingwhite.exec(prefix);
   		prefix = RegExp.$1 + " "
   	}
  }
  if (suffix==null || allwhite.test(suffix))
    suffix = ""
  else
  {
  	if (inSafari())
  	{
  		for (i=0;i<suffix.length;i++) if (!whitespace.test(suffix.charAt(i))) break;
  		suffix = " " + suffix.substring(i,suffix.length)
  	}
  	else
  	{
    	leadingwhite.exec(suffix)
    	suffix = " " + RegExp.$1
    }
  }
  var lms_string = get_lms_string(category)
  if (lms_string != "")
    windowRef.document.write(htmlencode(prefix + lms_string + suffix))
  else if (defaultString != null)
    windowRef.document.write(htmlencode(defaultString))
}
//-------------------------------------------------------------------------------
function make_nb_popup(wref, url)
{
  /* 
    Creates a popup window with instructions for the user to open/download
    a notebook file.  Parameters:
    * url  - This is a URL to the notebook file.  This can be either an absolute URL or
             a URL that's relative to the calling document (however, see the note below).
    * wref - This should be a reference to the window object of the calling document
             (in other words, pass "self" here).

    NOTE: For backwards compatibility, the function supports a calling protocol in which
    the wref argument is omitted.  In this case, the url argument (if it's a relative URL)
    is interpreted differently!  Specifically, it's interpreted as being relative to the
    COURSE ROOT folder, NOT relative to the calling document.  THIS USE IS DEPRECATED,
    because it causes problems when accessing pages that are outside of the course root folder.
  */
  make_savedoc_popup(wref, url, "notebook document", true)
}
//-------------------------------------------------------------------------------
function make_savedoc_popup(wref, url, docDescription, showSaveBlurb)
{
  /*
    Creates a popup window with instructions for the user to open/download a file.
    Use this in situations where the user should save a copy of the file, but it
    cannot be predicted whether the file will open in the user's browser (a good
    example of this is an RTF file).  Parameters:
      wref           - This should be a reference to the window object of the calling document
                       (in other words, pass "self" here).
      url            - The URL of the document to be saved.  This should either be an
                       absolute URL, or relative to the calling document (however, see
                       the note below).
      docDescription - (optional) A brief noun phrase which categorizes the type of document to
                       be saved (e.g. "notebook document").  This will be displayed in the
                       instructions that appear in the popup window.  If this parameter is
                       omitted, the word "document" will be used.
      showSaveBlurb  - (optional) A boolean.  If true, the window will include extra text and
                       a link to the appropriate "saveandsend.htm" document.

    NOTE: For backwards compatibility, the function supports a calling protocol in which
    the wref argument is omitted (i.e., the first parameter is the url).  In this case,
    the url argument (if it's a relative URL) is interpreted differently!  Specifically, 
    it's interpreted as being relative to the COURSE ROOT folder, NOT relative to the calling document.
    THIS USE IS DEPRECATED, because it causes problems when accessing pages that are outside of the course root folder.

    KNOWN PROBLEMS:
    ---------------
    On Windows Netscape 7.x, if showSaveBlurb is true and the course is being run using the "file:"
    protocol (rather than the "http:" protocol), the popoup window may be blank, and Netscape may
    (incorrectly) report that a remote file is trying to access a local file (specifically the findccfs.js
    file).

  */
  var doc_full_url, doc_folder_url

  if (typeof wref=="string")
  {
    // This is the "old-style" (deprecated) calling protocol.  Shift the other parameters.
    showSaveBlurb = docDescription
    docDescription = url
    url = wref
    // URL (if relative) is relative to course root folder
    doc_full_url = fullURL(url,self)
  }
  else
  {
    // URL (if relative) is relative to calling document.
    doc_full_url = fullURL(url,wref)
  }

  if (docDescription==null) docDescription = "document"
  if (showSaveBlurb==null) showSaveBlurb = false
  var rex = /[\\\/]?([^\\\/]+)$/
  var match_info = rex.exec(doc_full_url)
  var filename = match_info[1]

  doc_folder_url = doc_full_url.slice(0,-filename.length)
  // The newNotebookName global variable is an object which is defined in _CourseCommon/NewNotebookNames.js
  if (typeof newNotebookName!="undefined")
  {
    // For the course name, use the name of the folder containing the frameset.htm file.
    var rex = /[\\\/]([^\\\/]*)[\\\/]frameset\.htm.*$/i
    var matchArray = rex.exec(self.document.URL)
    if (matchArray==null)
      alert("Class.com Engineering Error: make_savedoc_popup: Expected the fs_utils.js file to be included ONLY in frameset.htm, not in " + self.document.URL)
    else
    {
      var course_identifier = matchArray[1].toLowerCase() + ":"
      for (var i=0; i<=1; i++) {
        var propName = [course_identifier+filename.toLowerCase(), filename.toLowerCase()][i]
        if (newNotebookName[propName]) {
          filename = newNotebookName[propName]
          doc_full_url = doc_folder_url + filename
          break
        }
      }
    }
  }

  var html = "<html>\n"
  if (showSaveBlurb) html += "<script src='" + fullURL("../_CourseCommon/findccfs.js", self) + "'></script>\n"
  html += "<body bgcolor=beige>\n"
  html += "<font face='Arial' size=3>\n"
  html += "<a href='" + doc_full_url + "'>Click here</a> to retrieve a fresh copy of your " + docDescription
  html += " (<b>" + filename + "</b>). Be sure to save it on a disk where you can easily locate it.<br><p>\n"
  html += "<b>NOTE:</b> If you've already saved a copy of this document, please open the saved copy instead.<br><p>\n"
  if (showSaveBlurb)
  {
    html += "If you need to review how to save and submit your work, including any images\n"
    html += "or graphics you wish to include as part of your project, refer to the\n"
    html += "<a href='javascript:CCFS.load_lms_popup(\"saveandsend.htm\")'>Saving and Submitting Assignments</a> instructions.<br><p>\n"
  }
  html += "<form>\n" //(required for NS 4.x)
  html += "<center><input type='button' value='Close' onClick='window.close()'></center>\n"
  html += "</form>\n" //(required for NS 4.x)
  html += "</font>\n"
  html += "</body>\n"
  html += "</html>\n"
  var w = window.open("", "", "width=600,height=400,resizable=yes,menubar=yes,scrollbars=yes,toolbar=yes")
  w.document.write(html)
  w.document.close()
}
//------------------------------------------------------------------------------
function load_lms_popup(filename)
{
  /*
    This function creates a new popup window and loads an LMS-specific version of the
    file indicated by filename into it.  It assumes that the file _LMS/docs/filename exists.
  */

  var url = fullURL("../_LMS/docs/" + filename, self)
  var w = window.open(url, "lms_popup", "width=600,height=400,scrollbars=yes,resizable=yes")
  w.focus()
}
//--------------------------------------------------------------------------------------------------------
function get_window_named(windowName, theWindow, fromWindow)
{
  /*
    Searches recursively for an open window or frame which has a window.name property equal to windowName.
    The search includes theWindow, and its "relatives."  Relatives are defined as follows:
    - A parent is a relative;
    - A "child" (element of frames array) is a relative;
    - An opener is a relative;
    - A relative of a relative is a relative.
    Parameters:
      windowName - String. The name of the window to be searched for.
      theWindow  - A reference to a window.  This window and all its relatives will be searched.
      fromWindow - Either null, or a relative of theWindow that has already been (or is being)
                   searched in another recursion, and therefore should be excluded from additional
                   searching.  This is to prevent infinite recursion back and forth between parent and child.
    
    If the window with name matching windowName is found, the function returns a reference to
    that window.  Otherwise, the function returns null.
  */
  var try_ccfs
  
  if (String(theWindow.name).toLowerCase() == String(windowName).toLowerCase() && !theWindow.closed) {
    return theWindow
  } else {
    // 1. Test parent (and its relatives):
    if (theWindow.I_AM_THE_CCFS == null && theWindow.parent != null && theWindow.parent != theWindow && theWindow.parent != fromWindow)
    {
      try_getWindow = get_window_named(windowName, theWindow.parent, theWindow)
      if (try_getWindow) return try_getWindow
    }
    // 2. Test opener (and its relatives):
    if (theWindow.opener != null && theWindow.opener != fromWindow)
    {
      try_getWindow = get_window_named(windowName, theWindow.opener, theWindow)
      if (try_getWindow) return try_getWindow
    }
    // 3. Test frames (and their relatives):
    for (var i=0; i<theWindow.frames.length; i++)
    {
      var theFrame = theWindow.frames[i]
      if (theFrame != fromWindow)
      {
        try_getWindow = get_window_named(windowName, theFrame, theWindow)
        if (try_getWindow) return try_getWindow
      }
    }
    // 4. If we got this far, it wasn't found:
    return null
  }
}

//=============================================================================================

function GetContentInfo(theToken)
{
  /*
    This function parses theToken, which is a string "token" that
    stands for some information about the current content.  The
    function returns the translated content information as a string.
    See the document "WriteContentInfo.doc" for a description of the
    syntax of the supported tokens.

    The function uses (but does not modify) the following global variables:
    XML_CONTENT_TITLE
    COURSE_TITLE
    CURRENT_PAGE_INFO
    CONTENT_FRAME
    STARTUP_PARMS
  */

  // If Yuna is not active (i.e. if STARTUP_PARMS.onlypage is not null) return a blank string:
  if (STARTUP_PARMS.onlypage) return ""

  var hlength, index, rex_result, token_type, token_arg, token_arg_int, i
  var errMsg = ""
  var returnString = ""
  if ((rex_result = /^\s*\{\s*([\s\S]*)\s*\}\s*$/.exec(theToken)) != null) theToken = rex_result[1] // strip enclosing curly brackets, if any
  if (/^\s*XMLT\s*$/i.test(theToken)) // {xmlt}
  {
    if (typeof XML_CONTENT_TITLE=="string")
      returnString = XML_CONTENT_TITLE
    else
      errMsg = "XML_CONTENT_TITLE was not correctly initialized."
  }
  else if (/^\s*TT\s*$/i.test(theToken)) // {tt}
  {
    if (typeof COURSE_TITLE=="string")
      returnString = COURSE_TITLE
    else
      errMsg = "COURSE_TITLE was not correctly initialized."
  }
  else
  {
    // All other tokens require a reference to CURRENT_PAGE_INFO
    if (typeof CURRENT_PAGE_INFO!="object" || CURRENT_PAGE_INFO==null)
      errMsg = "CURRENT_PAGE_INFO was not correctly initialized."
    else if (typeof CURRENT_PAGE_INFO.url != "string")
      errMsg = "CURRENT_PAGE_INFO.url was not correctly initialized."
    else
    {
      // Make sure that CURRENT_PAGE_INFO.url matches the content frame's URL.  If it doesn't, it means that the content
      // frame's contents were changed by a non-Yuna process (e.g. the browser "Back" button), and Yuna has not yet had
      // enough time to update the CURRENT_PAGE_INFO.url string.
      var urls = [CONTENT_FRAME.location.href, CONTENT_FRAME.document.URL]  //(try both--sometimes their values look slightly different)
      var need_refresh = true;
      for (i=0; i<urls.length; i++)
      {
        var test_url = urls[i]
        if (test_url.indexOf("#")!=-1) test_url = test_url.substring(0,test_url.lastIndexOf("#")) // Chop off hash (if any)
        if (test_url.indexOf("?")!=-1) test_url = test_url.substring(0,test_url.lastIndexOf("?")) // Chop off querystring (if any)
        test_url = test_url.replace(/\\/g,"/") // Flip backslashes to forward slashes
        if (test_url==CURRENT_PAGE_INFO.url)
        {
          need_refresh = false
          break;
        }
      }
      if (need_refresh && !isIE) //IE browsers are set (in function changePage in mainFrame.htm) to refresh the page always; so don't repeat it here.
      {
        // Load the page again: this will hopefully create enough of a delay so that CURRENT_PAGE_INFO.url
        // will have enough time to synchronize itself.
        //alert("CURRENT_PAGE_INFO.url="+CURRENT_PAGE_INFO.url+"\n              urls[0]="+urls[0]+"\n              urls[1]="+urls[1]) //============== DEBUGGING ============
        var dummy = setTimeout("CONTENT_FRAME.location.reload(true)", 100)
        returnString = ""
      }
    } // end if (CURRENT_PAGE_INFO and CURRENT_PAGE_INFO.url are well defined)
    if (errMsg=="")
    {
      if ((rex_result = /^\s*([TS])(\s*,\s*(\S[\s\S]*\S|\S))?\s*$/i.exec(theToken)) != null) // (titles & subtitles)
      {
        token_type = rex_result[1].toLowerCase()
        token_arg = rex_result[3]; if (token_arg==null) token_arg = ""
        if (!IsArray(CURRENT_PAGE_INFO.hierarchy))
          errMsg = "The hierarchy property of CURRENT_PAGE_INFO was not correctly initialized."
        else 
        {
          hlength = CURRENT_PAGE_INFO.hierarchy.length
          if (token_arg == "") // {t} or {s}
            index = hlength - 1
          else if (token_arg == "*") // {t,*} or {s,*}
          {
            for (index=hlength-1; index>=0; index--)
            {
              if (CURRENT_PAGE_INFO.hierarchy[index][(token_type=="t")?"title":"subtitle"] != "") break
            }
          }
          else if (!isNaN(token_arg)) // {t,n} or {s,n}
          {
            token_arg_int = Math.round(Number(token_arg))
            if (token_arg_int<0) index = hlength-1+token_arg_int; else index = token_arg_int - 1
            if (index > hlength-2) index = -1 // Prevent ",n" argument from returning page's title/subtitle
          }
          else
          {
            index = SearchCurrentPageInfo(token_arg)
          }
          if (index<0 || index>hlength-1)
            returnString = ""
          else
          {
            switch (token_type)
            {
              case "t":
                returnString = CURRENT_PAGE_INFO.hierarchy[index].title;
                break;
              case "s":
                returnString = CURRENT_PAGE_INFO.hierarchy[index].subtitle;
                break;
            } // end switch
          } // index in good range
        } // CURRENT_PAGE_INFO.hierarchy is an array
      } // token is a "T" type or "S" type
      else if (/^\s*P\s*$/i.test(theToken)) // {p}
      {
        if (typeof CURRENT_PAGE_INFO.page_number != "number")
          errMsg = "CURRENT_PAGE_INFO.page_number was not correctly initialized."
        else
          returnString = String(CURRENT_PAGE_INFO.page_number)
      }
      else if (/^\s*PP\s*$/i.test(theToken)) // {pp}
      {
        if (typeof CURRENT_PAGE_INFO.total_pages != "number")
          errMsg = "CURRENT_PAGE_INFO.total_pages was not correctly initialized."
        else
          returnString = String(CURRENT_PAGE_INFO.total_pages)
      }
      else
      {
        errMsg = "Can't interpret the token."
      }
    } // end if (errMsg=="")
  } // end if (token needs CURRENT_PAGE_INFO object)

  if (errMsg != "")
  {
    alert ("GetContentInfo: " + errMsg + " (Token: {" + theToken + "})")
    return ""
  }
  else
    return returnString
  //------ (nested function follows )---------------------------------
  function SearchCurrentPageInfo(theString)
  {
    /*
      This function looks in the CURRENT_PAGE_INFO.hierarchy array to find a tagname or a title
      in one of the current page's containers that equals or contains theString (case-insensitively).
      If such is found, the function returns the corresponding index of the CURRENT_PAGE_INFO.hierarchy array.
      Otherwise, the function returns -1
    */
    var string_lcase = theString.toLowerCase()
    var hlength = CURRENT_PAGE_INFO.hierarchy.length
    var index = -1
    var i
    // Search #1: look for a matching tagname
    for (i=hlength-2; i>=0; i--)
      if (string_lcase == CURRENT_PAGE_INFO.hierarchy[i].tagname.toLowerCase())
      {
        index = i
        break
      }
    // Search #2: look for a containing tagname
    if (index < 0)
    {
      for (i=hlength-2; i>=0; i--)
        if (CURRENT_PAGE_INFO.hierarchy[i].tagname.toLowerCase().indexOf(string_lcase)>-1)
        {
          index = i
          break
        }
    }
    // Search #3: look for a containing title
    if (index < 0)
    {
      for (i=hlength-2; i>=0; i--)
        if (CURRENT_PAGE_INFO.hierarchy[i].title.toLowerCase().indexOf(string_lcase)>-1)
        {
          index = i
          break
        }
    }
    return index
  } // end of function SearchCurrentPageInfo
} // end of function GetContentInfo
//=======================================================
function WriteContentInfo(theWindow, theString, defaultString)
{
  /*
    This function parses theString, which is assumed to include one or more special "tokens" plus (optionally)
    some plain text.  The tokens stand for special information about the course or about the current content page.
    Based on the contents of theString, the function generates an output string which is then written to the document
    in the window object represented by theWindow.

    The defaultString parameter is optional.  If all of the tokens in theString return blanks, the function writes
    defaultString (if present) to the document.

    Please see the document "WriteContentInfo.doc" for a complete description of how to use this function, including
    the syntax specification for theString.
  */
  var outString="", errMsg = "", escaping=false, inToken=false, cond_index=-1, blank_tokenResult=false, all_tokenResults_blank=true
  var i, c, tokenResult, tokenString

  if (theString.indexOf("{")==-1) theString = "{" + theString + "}" // treat the whole thing as a token if no curly brackets.
  for (i=0; i<theString.length && errMsg==""; i++)
  {
    c = theString.charAt(i);
    if (escaping)
    {
      escaping = false
      if (inToken) tokenString+=c; else outString+=c
    }
    else
    {
      switch (c)
      {
        case "`":
          escaping = true
          break;
        case "{":
          if (!inToken)
          {
            inToken = true
            tokenString = ""
          }
          else
            errMsg = "Invalid left curly bracket at position " + (i+1)
          break;
        case "}":
          if (inToken)
          {
            if ((tokenResult=GetContentInfo(tokenString))=="")
              blank_tokenResult = true;
            else
            {
              outString += htmlencode(tokenResult)
              all_tokenResults_blank = false
            }
            inToken = false
          }
          else
            errMsg = "Invalid right curly bracket at position " + (i+1)
          break;
        case "[":
          if (cond_index==-1) // not already in conditional string
          {
            cond_index = outString.length
            blank_tokenResult = false
          }
          else
            errMsg = "Invalid left square bracket at position " + (i+1)
          break;
        case "]":
          if (cond_index!=-1) // in conditional string
          {
            if (blank_tokenResult) outString = outString.substr(0, cond_index) // chop outString back to where conditional string started.
            cond_index = -1
          }
          else
            errMsg = "Invalid right square bracket at position " + (i+1)
          break;
        default:
          if (inToken) tokenString+=c; else outString += c
      } // end switch (c)
    } // end if (not escaping)
  }  // next i
  if (errMsg=="")
  {
    if (inToken)
      errMsg = "Missing a closing token delimiter (\"}\")."
    else if (cond_index!=-1)
      errMsg = "Missing a closing conditional-substring delimiter (\"]\")."
  }
  if (errMsg!="")
    alert("WriteContentInfo: Error in string argument: " + errMsg + "\n" + theString)
  else if (all_tokenResults_blank && (defaultString != null))
    theWindow.document.write(defaultString)
  else
    theWindow.document.write(outString)
}
//----------------------------------------------------------------------------------
function targetCourse(url, wref)
{
  /*
    Given a URL to a file or folder in some subfolder of cyclops (usually, a course folder),
    this function tries to determine which course the URL is in.  It returns the name of the
    course (or, as applicable, the name of the non-course cyclops subfolder, like "_CourseCommon").
    If the url does not seem to be within the cyclops folder, the function displays an alert and
    does not return any value.
    Parameters:

      url  - A relative or absolute URL.
      wref - An optional reference to a window object.  It's ignored if url is an absolute URL.
             if url is relative, and wref is specified, then url is interpreted as being relative
             to the document location of wref.  If url is relative and wref is omitted, then url
             is interpreted as being relative to the document that includes this function (presumably,
             frameset.htm in the course root folder).
  */
  if (wref==null) wref=self
  var theFullURL = fullURL(url, wref)
  var course_root_url = folderURL(self)
  var match = /(.*\/)[^\/]*\/$/.exec(course_root_url)
  var cyclops_url = match[1]
  if (theFullURL.substr(0,cyclops_url.length) != cyclops_url)
    alert("function targetCourse:\nThe specified URL (" + theFullURL + ") is not in the CYCLOPS folder.")
  else
  {
    var cyclops_relative_url = theFullURL.substr(cyclops_url.length) // chop the cyclops folder off.
    var slash = cyclops_relative_url.indexOf("/")
    if (slash==-1)
      return cyclops_relative_url // no slashes exist in cyclops_relative_url
    else
      return cyclops_relative_url.substr(0,slash) // return everything up to, but not including, the first slash
  }
}