// stuff I use all the time:

// - flexible sorting functions, including colSort
// - pageSetup, formSubmit
// - cool popup window technique
// - form variable gathering & setting functions
// - simple general-purpose ajax calling functions
// - rollover functions from youngpup.net
// - numerous extentions to String, Array, Date classes
// - cookie functions from x4


var isPC = (navigator.userAgent.indexOf("Windows") != -1);
var isMac = (navigator.userAgent.indexOf("Macintosh") != -1);
var isIE = (navigator.appName == "Microsoft Internet Explorer");
var isFirefox = (navigator.userAgent.indexOf("Firefox") != -1);
var f;

function init() {
	pageSetup();
	if (typeof formInit == 'function') {
		f = document.mainForm;
		formInit();
	}
	if (typeof initExtra == 'function') 
		initExtra();
}

// - - - -


function strCmp(a, b) {
	a += '';  b += '';  // make sure they're strings
	if (a > b) return 1;
	if (a < b) return -1;
	return 0;
}
// sort in descending order
function sortDesc(a, b) {
	return strCmp(a, b) * -1;
}
// sort case-insensitive
function sortCase(a, b) {
	a += '';  b += '';
	return strCmp(a.toLowerCase(), b.toLowerCase());
}
function sortCaseDesc(a, b) {
	a += '';  b += '';
	return strCmp(a.toLowerCase(), b.toLowerCase()) * -1;
}

// sort numerically (so 2 comes before 10)
//  note - doesn't make sense if values aren't all numbers
function sortNum(a, b)     { return a - b; }
function sortNumDesc(a, b) { return b - a; }


// to be used on arrays of hashes - sort by specified column index
function colSort(arr, col, type) {
	var funcs = { 'r': sortDesc, 'i':  sortCase,    'ir': sortCaseDesc, 'ri': sortCaseDesc, 
				  'n': sortNum,  'nr': sortNumDesc, 'rn': sortNumDesc };
	var func = funcs[type];
	if (typeof func != 'function') 
		func = strCmp;
	
	var hashCmp = function(a, b) { return func(a[col], b[col]); }
	return arr.sort(hashCmp);
}


// no need for jsobject - that's handled by obj.toSource()
//  and this is just for fun, really, as I can't think of a good
//  reason for javascript to be outputting php

function phpArray(obj, forceHash, depth) {
	if (typeof depth == 'undefined') 
		depth = 0;
	
	depth++;
	var indent = strRepeat('    ', depth);
	var a = [], v, key, out = '';
	
	if (forceHash || typeof obj == 'object') {
		for (k in obj) {
			v = obj[k];
			if (typeof v == 'function') 
				continue;
			key = isNumeric(k) ? k : q1(k);
			a.push(key +' => '+ phpArray(v, 0, depth));
		}
		out = 'array('+ a.join(",\n"+indent) +')';
	/*
	} else if (is_array(obj)) {
		foreach (obj as k => v) 
			a[] = phpArray(v, 0, 0);

		out = 'array('.join(', ', a).')';
	*/
	} else if (typeof obj == 'function') {
		out = '';
	} else {
		out = isNumeric(obj) ? obj : q1(obj);
	}
	
	depth--;
	return out;
}

function isNumeric(v) {
	var n = parseInt(v);
	return (n == v);
}
function q1(str) {
	return "'" + str.substitute("'", "\'") + "'";
}
function strRepeat(str, num) {
	var out = '', i;
	for (i = 0;  i < num;  i++)
		out += str;
	return out;
}
function isHash(obj) {
	if (typeof obj != 'object') 
		return false;
	for (var k in obj) {
		if (typeof obj[k] == 'function') 
			continue;
		if (!isNumeric(k)) 
			return typeof k + ' ' + k;
	}
	return false;
}

// - - - -



function pageSetup() {
	window.focus();
	soopaSetup();
	popSetup();

	if (!document.getElementsByTagName) return;
	
	// alternative to 'target="_blank"' for external links,
	//  because "target" has been removed from strict doctype spec
	var a, anchors = document.getElementsByTagName('a');
	for (var i = 0;  i < anchors.length;  i++) {
		a = anchors[i];
		if (a.getAttribute('href') && a.getAttribute('rel') == 'ext') 
			a.target = '_blank';
	}
}

function hiFirst(formName) {
	var f = (formName && formName != '') ? eval('document.'+ formName) : document.forms[0];
	if (f == null) return;
	
	for (var i = 0;  (e = f.elements[i]);  i++) {
		if ((e.type == 'text' || e.type == 'password' || e.type == 'textarea') && e.value == '') {
			e.focus();
			break;
		}
	}
}


function formSubmit(doValidate, passValue) {
	
	if (passValue == 'close' || passValue == 'cancel') {
		window.close();
		return false;
	}
	if (doValidate && !validate()) 
		return false;

	if (doValidate && typeof valExtra == 'function' && !valExtra()) 
		return false;
	
	if (typeof passValue != 'undefined') 
		f.submit_value.value = passValue;
	
	f.submit();
	return true;
}











// compact way of opening new custom windows
// construct simple link with href as usual, 
//   and set target="pop:winname:w,h[,t,l]:attrs"
//   e.g. pop:content:640,440,20,20:yynnn

var popAttrs  = ['resizable', 'scrollbars', 'location', 'toolbar', 'status'];
var popCoords = ['width', 'height', 'top', 'left'];

function popSetup() {
	var a;
	for (var i = 0;  (a = document.links[i]);  i++) {
		if (a.target && a.target.indexOf("pop:") == 0) {
			a.onclick = axPop;
		}
	}
	// could also go through forms and set onsubmit events
}

function popSpecs(str) {
	var attrs = [], attrstr = "";
	var a = str.split(":");
	
	if (a.length > 2) {
		var i, c, coords = a[2].split(',');
		for (i = 0;  (c = coords[i]);  i++) {
			attrs.push(popCoords[i] +'='+ c);
		}
	}
	if (a.length > 3) {
		var on, attrstr = a[3];
		for (i = 0;  i < attrstr.length;  i++) {
			on = (attrstr.substring(i, i+1) == 'n') ? 'no' : 'yes';
			attrs.push(popAttrs[i] +'='+ on);
		}
	}
	return attrs.join(',');
}

function axPop() {
	var a = this.target.split(":");
	var winname = a[1];
	var specs = popSpecs(this.target);
	
	// code to center window on screen
	//  (actually center horiz, a bit above center vert = eye level)
	/*
	var tmp, ref;
	if (specs.indexOf("left=") == -1) {
		tmp = specs.match(/width=(\d+)/);
		if (tmp[1] > 0) {
			var l = Math.floor((screen.availWidth - tmp[1])/2);
			specs += ',left='+ l;
		}
	}
	if (specs.indexOf("top=") == -1) {
		tmp = specs.match(/height=(\d+)/);
		if (tmp[1] > 0) {
			var t = Math.floor((screen.availHeight - tmp[1])/5);
			specs += ',top='+ t;
		}
	}
	//alert('specs: '+ specs);
	*/
	//var url = (this.href) ? this.href : this.action;
	
	ref = window.open(this.href, winname, specs);
	if (ref.opener == null) 
		ref.opener = window;
	ref.opener.name = 'opener';
	ref.focus();
	eval(winname + ' = ref;');
	return false;
}




// for those of you who prefer spaces escaped with '+'...
function urlencode(str) {
	str = escape(str);
	return str.substitute("%20","+");
}

function urldecode(arr) {
	var out = [];
	
	if (typeof arr == 'string') 
		arr = arr.split(",");
	
	for (var i = 0;  (a = arr[i]);  i++) 
		out.push(unescape(a).substitute("+"," "));
	
	return out;
}

function stripHTML(str) {
	var re = /<\S[^><]*>/g;
	return str.replace(re, "");
}



function formEls(name) {
	var els = document.getElementsByName(name);
	if (!els || els.length == 0) 
		els = document.getElementsByName(name+'[]');  // for php array fields
	return els;
}

function clearSel(name) {
	var sels = formEls(name);
	if (!sels) {
		alert('clearSel no sels, name: ' + name);
		return false;
	}
	var sel = sels[0];
	if (!sel) {
		alert('clearSel invalid name: ' + name);
		return false;
	}
	if (sel.options) 
	//	sel.options = [];
		sel.options.length = 0;
	//sel.onchange = null;
	//sel.disabled = true;
	return true;
}



// assemble query string
// either specify form variables and optionally specify form name
// or pass '*' for all varNames and specify form name (will take 1st form if empty)
// (could expand this to any/all forms... I'll do that if I ever need it)

function getqs(varNames, formName, blanks) {
	var v, vn, vv, e, vars = [], out = [];
	if (!varNames || varNames == '*') {
		var f = (formName && formName != '') ? eval('document.'+ formName) : document.forms[0];
		for (var i = 0;  (e = f.elements[i]);  i++) 
			if (!vars.contains(e.name)) 
				vars.push(e.name);
	} else vars = varNames.split(',');
	
	for (var i = 0;  (v = vars[i]);  i++) {
		vn = v.replace(/[\[\]]+/, '');  // '[]' part is just for php
		vv = val(v, formName);
		if (vv != '' || blanks) 
			out.push(vn +'='+ vv);
	}
	return out.join('&');
}

function showQS() {
	var str = getqs();
	str = str.substitute('&', '; ');
	alert(str);
	return false;
}


// now need function to take qs and deposit values in form fields...
/*
function putqs(varNames, formName) {
	
}
*/


//function varVals(varName, formName) {

function val(varName, formName) {
	var e, v = false, opt, opts, out = [];
	
	var els = formEls(varName);
	if (!els || els.length == 0) return false;
	
	for (var i = 0;  (e = els[i]);  i++) {
		opts = [];
		if (formName && formName != '' && e.form.name != formName) continue;
		
		switch (e.type) {
			case 'checkbox':
			case 'radio':
				if (e.checked) 
					v = e.value;
				else continue;
				break;

			case 'select-one':
				if (e.options.length > 0) 
					v = e.options[e.selectedIndex].value;
				else continue;
				if (v == ' ' || v.match(/select\.\.\.$/i)) v = '';
				break;
			
			case 'select-multiple':
				if (e.options.length > 0) {
					for (var j = 0;  (opt = e.options[j]);  j++) 
						if (opt.selected) 
							opts.push(urlencode(opt.value));
					v = opts.join(',');
				} else continue;
				break;
			
			//case 'text':  case 'textarea':
			//case 'hidden':  case 'password':  case 'button':
			default:
				v = e.value;
		}
		out.push((opts.length > 0) ? v : urlencode(v));
	}
	return out.join(',');
}





function setVal(varName, v) {
	var j, o, vals, done = false, ch = false;
	
	var els = formEls(varName);
	if (!els || els.length == 0) return false;
	
	for (var i = 0;  (e = els[i]);  i++) {
		switch (e.type) {
			case 'checkbox':
				ch = true;
				vals = urldecode(v);
				if (vals.contains(e.value)) 
					e.checked = true;
				else e.checked = false;
				
				// not done - continue to loop
			
			case 'radio':
				if (v == e.value) {
					e.checked = true;
					done = true;
				}
				break;
			
			case 'select-one':
				if (e.options.length > 0) {
					if (v == '' || v == 0) {
						e.selectedIndex = 0;
						done = true;
						break;
					}
					for (j = 0;  j < e.options.length;  j++) {
						if (v == e.options[j].value) {
							e.selectedIndex = j;
							done = true;
							break;
						}
					}
				} else {
					vals = urldecode(v);
					for (j = 0;  j < vals.length;  j++) {
						e.options[j] = new Option(vals[j], vals[j]);
					}
					//e.selectedIndex = 0;
					done = true;
				}
				break;
			
			case 'select-multiple':
				vals = urldecode(v);
				
				if (e.options.length > 0) {
					for (j = 0;  j < e.options.length;  j++) {
						e.options[j].selected = (vals.contains(e.options[j].value));
					}
					done = true;
				
				} else {
					for (j = 0;  j < vals.length;  j++) {
						e.options[j] = new Option(vals[j], vals[j]);
					}
					
					//e.options[j-1].selected = true;
					done = true;
				}
				break;
			
			//case 'text':    case 'textarea':
			//case 'hidden':  case 'password':  case 'button':
			default:
				e.value = v;
				done = true;
		}
		if (done) break;
	}
	return ch || done;
}





// - - - -
// Simplest possible ajax functions, made general-purpose

var reqFunc, req, response;

// parameter is URL string (relative or complete) to
// an .xml file whose Content-Type is a valid XML
// type, such as text/xml; XML source must be from
// same domain as HTML file
function loadXMLDoc(url, func) {
	reqFunc = func;
	// branch for native XMLHttpRequest object
	if (window.XMLHttpRequest) {
		req = new XMLHttpRequest();
		req.onreadystatechange = processReqChange;
		req.open("GET", url, true);
		req.send(null);
	// branch for IE/Windows ActiveX version
	} else if (window.ActiveXObject) {
		isIE = true;
		req = new ActiveXObject("Microsoft.XMLHTTP");
		if (req) {
			req.onreadystatechange = processReqChange;
			req.open("GET", url, true);
			req.send();
		}
	}
}

// handle onreadystatechange event of req object
function processReqChange() {
	if (req.readyState == 4) {    // only if req shows "loaded"
		if (req.status == 200) {  // only if "OK"
			eval(reqFunc);
		} else {
		 	return req.statusText;
		}
	}
}





// - - - -
// soopa-rollovers
// 7/28/2001
// www.youngpup.net

function soopaSetup() {
	var img, sh, sn, sd;
	for (var i = 0;  (img = document.images[i]);  i++) {
		if (img.getAttribute) {

			sn = img.getAttribute("src");
			sh = img.getAttribute("hsrc");
			sd = img.getAttribute("dsrc");

			if (sn != "" && sn != null) {
				img.n = new Image();
				img.n.src = img.src;
			
				if (sh != "" && sh != null) {
					img.h = new Image();
					img.h.src = sh;
					img.onmouseover = soopaSwapOn;
					img.onmouseout  = soopaSwapOff;
				}

				if (sd != "" && sd != null) {
					img.d = new Image();
					img.d.src = sd;
					img.onmousedown = soopaSwapDown;
				}
			}
		}
	}
}
function soopaSwapOn() {
	this.src = this.h.src;
}
function soopaSwapOff() {
	this.src  = this.n.src;
}
function soopaSwapDown() {
	this.src  = this.d.src;
	this.temp = typeof(document.onmouseup) != "undefined" && typeof(document.onmouseup) != "unknown" ? document.onmouseup : "";
	soopaSwapUp.img = this;
	document.onmouseup = soopaSwapUp;
}
function soopaSwapUp() {
	var ths = soopaSwapUp.img;
	ths.src = ths.n.src;
	if (ths.temp) document.onmouseup = ths.temp;
}






// like Trim( ) in vbscript. removes trailing and 
// leading whitespace from a string
String.prototype.trim = function() {
	return this.replace(/^\s*|\s*$/g, "");
}

function pad(str, ch, num, side) {
	if (!ch) ch = ' ';
	if (!(num > 0)) num  =  2;
	if (!side) side = 'l';
	str = str + "";
	var len = num - str.length;
	if (side == 'r') 
		for (var i = 0;  i < len;  i++) 
			str += ch;
	else 
		for (var i = 0;  i < len;  i++) 
			str = ch + str;
	return str;
}


// determines whether or not a filename has one of the specified extensions
String.prototype.endsWith = function() {
	var bOk = false;
	for (var i = 0;  i < arguments.length;  i++) {
		if (this.indexOf(arguments[i]) == this.length - arguments[i].length) {
			bOk = true;
			break;
		}
	}
	return bOk;
}

// quick shortcut to grab parenthetical match
String.prototype.grab = function(reg) {
	//eval(reg);
	var tmp = this.match(reg);
	if (tmp && tmp.length > 0) 
		return (tmp[1]) ? tmp[1] : false;
	else return false;
}

String.prototype.substitute = function(was, becomes) {
	return this.split(was).join(becomes);
}

String.prototype.squashChar = function(pos, c) {
	return this.substring(0, pos) + c + this.substring(pos + 1);
}



// push is a quite useful method of arrays in newer 
// javascript implementations, but not in ie5-
Array.prototype.push = function(v) {
	this[this.length] = v;
	return v;
}
Array.prototype.contains = function(v) {
	for (i = 0;  i < this.length;  i++) 
		if (this[i] == v) 
			return true;
	return false;
}
Array.prototype.without = function(v) {
	var out = [];
	for (i = 0;  i < this.length;  i++) 
		if (this[i] != v) 
			out.push(this[i]);
	return out;
}
Array.prototype.unset = function(k) {
	var out = [];
	for (i = 0;  i < this.length;  i++) 
		if (i != k) 
			out.push(this[i]);
	return out;
}
Array.prototype.intersect = function(arr) {
	var el, out = [];
	for (var i = 0;  (el = this[i]);  i++) 
		if (arr.contains(el)) 
			out.push(el);
	return out;
}







// Extensions to the Date class:
// var myDate = new Date();
// myDate.getMonthName(); -> "February"
// myDate.getHumanDateString(); -> "29 February 2000"
// myDate.getHumanTimeString(); -> "4:00 pm"


// returns the name of the month
Date.prototype.getMonthName = function() {
	return ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug",
			"Sep","Oct","Nov","Dec"][this.getMonth()];
}

// returns a nicely formatted date string
Date.prototype.getHumanDateString = function() {
	return this.getDate() + " " + this.getMonthName() + " " + this.getFullYear();
}

// returns a nicely formatted time string
Date.prototype.getHumanTimeString = function() {
	var h = this.getHours();
	var m = this.getMinutes();
	var t = h >= 12 ? "pm" : "am";

	if (h == 0) h = 24;
	if (h > 12) h -= 12;
	h = String(h);
	m = String(m);
	if (m.length == 1) m = "0" + m;

	return h + ":" + m + " " + t;
}

// returns a mysql-style timestamp
Date.prototype.getTimestamp = function() {
	var y = this.getFullYear();
	var m = pad(this.getMonth() + 1, '0');
	var d = pad(this.getDate(), '0');
	var h = pad(this.getHours(), '0');
	var i = pad(this.getMinutes(), '0');
	var s = pad(this.getSeconds(), '0');
	return y+"-"+m+"-"+d+" "+h+":"+i+":"+s;
}


function now() {
	var d = new Date();
	return d.getTimestamp();
}




// Time is a wrapper class for the Date object
// it accepts a wide variety of strings representing the time
// and returns a date object representing the current date with the
// specified time.
//
// var myTime = new Time('4p');
// myTime now contains a date object representing today at 4PM.
//
// accepted formats include 4p, 4:p, 4:1p, 4:10p, 4:10pm, 16:00, 
// and a bunch more...

function Time(sTimeStr) {
	if (sTimeStr.trim() != "") {
		var a = sTimeStr.match(/\d{1,2}/g);
		var t = sTimeStr.match(/(am|pm|a|p)/ig);
		if (a) {
			var d = new Date();
			var h = a[0];
			var m = a[1] ? a[1] : 0;
			if (t) t = String(t[0]);
			if (t && t.charAt(0).toLowerCase() == "p") h = h % 12 + 12;
			d.setHours(h);
			d.setMinutes(m);
			d.setSeconds("0");
			return d;
		}
		return null;
	}
}


// ugly way of simulating sleep() - just eat CPU

function pause(ms) {
	var now = new Date();
	var fin = now.getTime() + ms;
	while (true) {
		now = new Date();
		if (now.getTime() > fin)
			return;
	}
}

var sleeping = false;

function sleep(ms) {
	sleeping = true;
	setTimeout('stopSleeping', 500);
	while (sleeping) {
		noop();
	}
}
function stopSleeping() {
	sleeping = false;
}
function noop() {
	return;
}



function loadFrame(pFrame, pURL) {
	var theFrame = '';
	if (!(frameObject = document.getElementById(pFrame)))
		return;
	if (frameObject.contentWindow) {  // IE5.5 - 6
	    theFrame = frameObject.contentWindow;
	} else if (frameObject.document) {  // IE5
		theFrame = frameObject.document;
	} else if (frameObject.contentDocument) {  // NS6
		theFrame = frameObject.contentDocument;
	}
	if (theFrame) {
	    theFrame.location.replace(pURL);
	} else {
		frames[pFrame].location = pURL;
	}
}



/* x_cook.js compiled from X 4.0 with XC 0.27b. Distributed by GNU LGPL. For copyrights, license, documentation and more visit Cross-Browser.com */
function xDeleteCookie(name, path){
	if (xGetCookie(name)) {document.cookie = name + "=" +"; path=" + ((!path) ? "/" : path) +"; expires=" + new Date(0).toGMTString();}
}
function xGetCookie(name){
	var value=null, search=name+"=";if (document.cookie.length > 0) {var offset = document.cookie.indexOf(search);if (offset != -1) {offset += search.length;var end = document.cookie.indexOf(";", offset);if (end == -1) end = document.cookie.length;value = unescape(document.cookie.substring(offset, end));}}return value;
}
function xSetCookie(name, value, expire, path){
	document.cookie = name + "=" + escape(value) +((!expire) ? "" : ("; expires=" + expire.toGMTString())) +"; path=" + ((!path) ? "/" : path);
}




// - - - - -

// This code was written by Tyler Akins and has been placed in the
// public domain.  It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com


var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

function encode64(input) {
   var output = "";
   var chr1, chr2, chr3;
   var enc1, enc2, enc3, enc4;
   var i = 0;

   do {
      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
         enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
         enc4 = 64;
      }

      output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + 
         keyStr.charAt(enc3) + keyStr.charAt(enc4);
   } while (i < input.length);
   
   return output;
}


function decode64(input) {
   var output = "";
   var chr1, chr2, chr3;
   var enc1, enc2, enc3, enc4;
   var i = 0;

   // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
   input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

   do {
      enc1 = keyStr.indexOf(input.charAt(i++));
      enc2 = keyStr.indexOf(input.charAt(i++));
      enc3 = keyStr.indexOf(input.charAt(i++));
      enc4 = keyStr.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      output = output + String.fromCharCode(chr1);

      if (enc3 != 64) {
         output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
         output = output + String.fromCharCode(chr3);
      }
   } while (i < input.length);

   return output;
}


