	// Common routines for San Juan Nature Institute web site

	//*****************************************************************************************************************
	//	Initialize site-wide javascript constants
	//*****************************************************************************************************************

	// Flags to indicate whether Lecture Series pages for each island exist
	var sjni_lectures_SJ = true;
	var sjni_lectures_Orcas = false;
	var sjni_lectures_Lopez = false;

	//*****************************************************************************************************************
	//	Navigation Menus for San Juan Nature Institute (Intended to work with Macromedia Dreameaver routine mm_menu.js)
	//*****************************************************************************************************************

	// Function to assign customized properties to a menu object
	function SJNI_MenuSettings(menuobj) {
		menuobj.hideOnMouseOut=true;
		menuobj.fontWeight="normal";							// font weight, e.g. bold | normal					
		menuobj.fontStyle="normal";								// font style, e.g. italic | normal
		menuobj.childMenuIcon="/images/arrow_w.gif";			// path to icon indicating a menu exists
//		menuobj.bgColor="#cccccc";								// item bottom and right (inside b/r) border color
//		menuobj.menuLiteBgColor='#ffffff';						// item top and left border (inside top/left) color
		menuobj.bgColor="#99cc99";								// item bottom and right (inside b/r) border color
		menuobj.menuLiteBgColor='#99cc99';						// item top and left border (inside top/left) color
		menuobj.menuBorder=1;									// border size (pixels)
		menuobj.menuBorderBgColor='#666666';					// menu frame (outside top/bottom/left/right) border color
	}

	// Build the menu tree. Each menu is created as an object using the object prototype in mm_menu.js
	function SJNI_LoadMenus() {
		// do this function only once
		if (window.sjni_programs) return;
	
		// create the menu objects, starting with the lowest levels and working up to the root.
		// Args: label (topmost menu is labeled "root"), menuwidth, menuitemheight, fontfamily, fontsize(in px, not pt), fontcolor, fontcolor-Hilite, item-Bgcolor, itemHilite-Bgcolor, item-halign, item-valign, item-padding, item-spacing, hide-timeout(ms), submenu-xoffset, submenu-yoffset, submenuRelativeToItem(T/F), menuBgOpaque(T/F), vertical?(T/F), itemIndent, Unused(T/F), Unused(T/F)
		window.sjni_programs = new Menu("root",190,18,"arial,helvetica,sans-serif",12,"#000000","#000000","#99cc99","#ffffcc","left","middle",3,0,400,-7,7,true,true,true,0,true,true);
		window.sjni_shoppingcart = new Menu("root",80,18,"arial,helvetica,sans-serif",12,"#000000","#000000","#99cc99","#ffffcc","left","middle",3,0,400,-7,7,true,true,true,0,true,true);

		SJNI_MenuSettings(window.sjni_programs);
		SJNI_MenuSettings(window.sjni_shoppingcart);

		sjni_programs.addMenuItem('<span class="menutext">&bull;&nbsp;&nbsp;Adult Field Workshops</span>',"window.location='/adult.php'");
		sjni_programs.addMenuItem('<span class="submenutext">List Workshops by Date</span>',"window.location='/list.php?sort=d'");
		sjni_programs.addMenuItem('<span class="submenutext">List Workshops by Island</span>',"window.location='/list.php?sort=i'");
		sjni_programs.addMenuItem('<span class="submenutext">List Workshops by Subject</span>',"window.location='/list.php?sort=s'");
		sjni_programs.addMenuItem('<span class="submenutext">Fill Out Evaluation Form</span>',"window.location='/eval.php'");
		sjni_programs.addMenuItem("<span class='menutext'>&bull;&nbsp;&nbsp;Young Naturalist Programs</span>","window.location='/youth.php'");
		sjni_programs.addMenuItem('<span class="menutext">&bull;&nbsp;&nbsp;Arthur Whiteley Lecture Series</span>',"window.location='/lecture.php'");
		if (sjni_lectures_SJ) { sjni_programs.addMenuItem('<span class="submenutext">Lectures on San Juan Island</span>',"window.location='/lecture_sanjuan.php'"); } else { /*sjni_programs.addMenuItem('<span class="submenutextdim">Lectures On San Juan Island</span>',''); */ }
		if (sjni_lectures_Orcas) { sjni_programs.addMenuItem('<span class="submenutext">Lectures on Orcas Island</span>',"window.location='/lecture_orcas.php'"); } else { /* sjni_programs.addMenuItem('<span class="submenutextdim">Lectures On Orcas Island</span>',''); */ }
		if (sjni_lectures_Lopez) { sjni_programs.addMenuItem('<span class="submenutext">Lectures on Lopez Island</span>',"window.location='/lecture_lopez.php'"); } else { /* sjni_programs.addMenuItem('<span class="submenutextdim">Lectures On Lopez Island</span>',''); */ }
		sjni_programs.addMenuItem("<span class='menutext'>&bull;&nbsp;&nbsp;Partners in Science</span>","window.location='/k12.php'");

		sjni_shoppingcart.addMenuItem("<span class='menutext'>&bull;&nbsp;&nbsp;Edit Cart</span>","window.location='/cart.php'");
		sjni_shoppingcart.addMenuItem("<span class='menutext'>&bull;&nbsp;&nbsp;Check Out</span>","window.location='/checkout.php'");

		// write the menus to the page
		sjni_programs.writeMenus();
	}

	//*****************************************************************************************************************
	//	Javascript functions
	//*****************************************************************************************************************

	// find an object in the DOM
	// Source: Adapted from Macromedia Studio MX v6.1
	function SJNI_findObj(n, d) { //v4.01
	  var p,i,x;  if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {
	    d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}
	  if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];
	  for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=SJNI_findObj(n,d.layers[i].document);
	  if(!x && d.getElementById) x=d.getElementById(n); return x;
	}

	// preload images into the DOM (avoids download delay on first mouse event)
	// Source: Adapted from Macromedia Studio MX v6.1
	function SJNI_preloadImages() { //v3.0
	  var d=document; if(d.images){ if(!d.SJNI_p) d.SJNI_p=new Array();
	    var i,j=d.SJNI_p.length,a=SJNI_preloadImages.arguments; for(i=0; i<a.length; i++)
	    if (a[i].indexOf("#")!=0){ d.SJNI_p[j]=new Image; d.SJNI_p[j++].src=a[i];}}
	}

	// swap an image with another (used with event such as MouseOver)
	// Source: Adapted from Macromedia Studio MX v6.1
	function SJNI_swapImage() { //v3.0
	  var i,j=0,x,a=SJNI_swapImage.arguments; document.SJNI_sr=new Array; for(i=0;i<(a.length-2);i+=3)
	   if ((x=SJNI_findObj(a[i]))!=null){document.SJNI_sr[j++]=x; if(!x.oSrc) x.oSrc=x.src; x.src=a[i+2];}
	}

	// unswap an image, restore it to the original image source (used with event such as MouseOut)
	// Source: Adapted from Macromedia Studio MX v6.1
	function SJNI_swapImgRestore() { //v3.0
	  var i,x,a=document.SJNI_sr; for(i=0;a&&i<a.length&&(x=a[i])&&x.oSrc;i++) x.src=x.oSrc;
	}

	// returns TRUE if input string contains only numerics, otherwise returns FALSE
	function isOnlyNumbers(inputString) {
		var searchForNumbers = /\D+/;							// regular expression - matches any non-number character
		if (!searchForNumbers.test(inputString)) return true;	// if .test method result is true, string contains at least one non-numeric
		return false;
	}

	// Set the CSS class name for a specified object
	function chgClass(whichID,theClass) {
		var theObj=SJNI_findObj(whichID);
		if (theObj!=null) theObj.className=theClass;
	}

	// Converts Ascii Unicode value to character (string). Operates as in VB Chr() function.
	function chr(asciivalue) {
		return String.fromCharCode(asciivalue);
	}

	// detects and forces a false result if the enter key is pressed while a field has focus
	function trapEnterKeyPress(evt) {
		if ((evt)&&(evt.which)) {					// if 'which' property of event object is supported (NN4)
			if (evt.which==13) return false;		// if enter key, return false to suppress automatic submit (e.g.ignore this keystroke)
		}
		if (evt.keyCode==13) return false;			// if browser doesn't recognise the 'which' property try the 'keycode' property  (IE)
		return true;								// return true to pass the keystroke
	}

	// formats a numeric amount into two decimal positions with commas, suitable for monetary amounts.
	function currencyFormatter(amount, rounded) {
		var i=parseFloat(amount);
		if (isNaN(i)) { i = 0.00; }
		var minus = '';
		if (i<0) { minus = '-'; }
		i = Math.abs(i);
		if (rounded) {
			i = parseInt((i + .005) * 100);
			i = i / 100;
		}
		s = new String(i);
		if (s.indexOf('.')<0) { s += '.00'; }
		if (s.indexOf('.')==(s.length-2)) { s += '0'; }
		var delimiter = ",";
		var a = s.split('.',2)
		var i = parseInt(a[0]);		// i = integer portion
		var d = a[1];				// d = decimal portion
		var n = new String(i);
		var a = [];
		while (n.length>3) {
			var nn = n.substr(n.length-3);
			a.unshift(nn);
			n = n.substr(0,n.length-3);
		}
		if (n.length>0) { a.unshift(n); }
		n = a.join(delimiter);
		if (d.length<1) { 
			amount = n;
		} else { 
			amount = n + '.' + d;
		}
		amount = minus + amount;
		return amount;
	}

	// Determine if an e-mail address is valid **************************************************************************************************************************************************************
	// Source: The JavaScript Source!! http://javascript.internet.com -->
	// V1.1.3: Sandeep V. Tamhankar (stamhankar@hotmail.com) -->
	function validateEmail(emailStr) {
		var checkTLD=1;															// Indicates whether to check for recognized Top-Level-Domain (TLD) (eg com, net, org, etc) 1=Yes, 0=No
		var knownDomsPat=/^(com|net|org|edu|int|mil|gov|arpa|biz|aero|name|coop|info|pro|museum)$/;		// regular expression of known TLDs
		var emailPat=/^(.+)@(.+)$/;												// regular expression for user@domain
		var specialChars="\\(\\)><@,;:\\\\\\\"\\.\\[\\]";						// defines allowed special characters, which are ( ) < > @ , ; : \ " . [ ]
		var validChars="\[^\\s" + specialChars + "\]";							// defines disallowed characters in a user name or domain name.
		var quotedUser="(\"[^\"]*\")";											// used to detect quoted string (allows anything between quotes)
		var ipDomainPat=/^\[(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\]$/;		// regular expression to recognize IP address format (eg. user@[123.123.123.123] )
		var atom=validChars + '+';												// match pattern for a series of non-special characters
		var word="(" + atom + "|" + quotedUser + ")";							// match pattern represents one word in the typical username. For example, in john.doe@somewhere.com, john and doe are words.
		var userPat=new RegExp("^" + word + "(\\." + word + ")*$");				// pattern to describe the structure of the user
		var domainPat=new RegExp("^" + atom + "(\\." + atom +")*$");			// pattern to describe normal symbolic domain name (not IP format)
			// Begin with the coarse pattern to simply break up user@domain into different pieces that are easy to analyze
		var matchArray=emailStr.match(emailPat);
		if (matchArray==null) {
			// Too many/few @'s or something; basically, this address doesn't even fit the general format of a valid e-mail address
			// alert("Email address seems incorrect (check @ and .'s)");
			return false;
		}
		var user=matchArray[1];
		var domain=matchArray[2];
			// Check if only basic ASCII characters are in the strings (0-127).
		for (i=0; i<user.length; i++) {
			if (user.charCodeAt(i)>127) {
				// alert("Ths username contains invalid characters.");
				return false;
	  			}
		}
		for (i=0; i<domain.length; i++) {
			if (domain.charCodeAt(i)>127) {
				// alert("Ths domain name contains invalid characters.");
				return false;
  				}
		}
			// See if "user" is valid 
		if (user.match(userPat)==null) {
			// alert("The username doesn't seem to be valid.");
			return false;
		}
			// If the e-mail address is at an IP address make sure the IP address is valid.
		var IPArray=domain.match(ipDomainPat);
		if (IPArray!=null) {
			for (var i=1;i<=4;i++) {
				if (IPArray[i]>255) {
					// alert("Destination IP address is invalid!");
					return false;
  					}
			}
		return true;		// Domain is IP address and it's ok - we're done.
		}
		// Domain is symbolic name.  Check if it's valid.
		var atomPat=new RegExp("^" + atom + "$");
		var domArr=domain.split(".");
		var len=domArr.length;
		for (i=0;i<len;i++) {
			if (domArr[i].search(atomPat)==-1) {
				// alert("The domain name does not seem to be valid.");
				return false;
  				}
		}
		// Make sure that domain ends in a known top-level domain (like com, edu, gov) or a two-letter word,
		// representing country (uk, nl), and that there's a hostname preceding the domain or country.
		if (checkTLD && domArr[domArr.length-1].length!=2 && domArr[domArr.length-1].search(knownDomsPat)==-1) {
			// alert("The address must end in a well-known domain or two letter " + "country.");
			return false;
		}
		// Make sure there's a host name preceding the domain.
		if (len<2) {
			// alert("This address is missing a hostname!");
			return false;
		}
		// If we've gotten this far, everything's valid!
		return true;
	}

	// validate a new cart entry by checking for database inconsistencies
	// returns true if valid, false if not
	function validCart(theEntry) {
		var cartdetail, costoption, costarg, DBobj, costitem, optcount, ii;
		optcount=0;
		if ((theEntry==null)||(theEntry.length==0)) return false;								// a null entry is considered invalid
		cartdetail=theEntry.split(':');															// split the entry into it's details, delimiter is :
		if (cartdetail.length!=3) return false;													// there should be 3 and only 3 entries in the cartdetail table
		costoption=cartdetail[1].split(',');													// split cart detail 1 (cost options selected) into costoption table
		costarg=cartdetail[2].split(',');														// split cart detail 2 (cost arguments, e.g. qty) into costarg table
		if ((costoption.length==0)||(costarg.length==0)) return false;							// check for empty cost options or empty cost args tables exit with false result
		DBobj=fetchDBobj(cartdetail[0]);														// cart detail 0 is the database key - fetch the database object
		if (DBobj==null) return false;															// if no DB object is found exit with false result
		costitem=DBobj.cost.split('|');															// split the database cost info into the costitem table
		if ((costitem.length==0)||(costitem[0]==null)) return false;							// if the cost item table is empty exit with false result code
		if (costitem.length!=costoption.length) return false;									// if the number of cost elements in DB does not match the number of options in the cart entry, we have a database inconsistency; exit with false result code
		for (ii=0; ii<costoption.length; ii++) { if (costoption[ii]) optcount=optcount+1; }		// count the number of options selected
		if (optcount==0) return false;															// if no options selected (e.g. all false), exit with false result code											
		return true;
	}
