/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * Filename: util.js															*
 * Author:	Keith Borgmann														*
 * Date:	2008-05-21															*
 * Version:  1.1.1																*
 * Description:																	*
 *	Miscellaneous js utilities/functions										*
 *																				*
 * Function List:																*
 *	saveURL(strFilename, strURL);			*1									*
 *	extendFunction(element, functionName, newFunction);							*
 *																				*
 *	centeredPopup(url, width, height[, name[, blnScrollbars]]);					*
 *	dialogbox(url, callbackName, width, height[, name[, blnScrollbars]]);		*
 *																				*
 *	getRadioIndexByName(rad, strName);											*
 *	getSelectIndex(sel);														*
 *	getSelectValue(sel[, optionAttribute]);										*
 *	setSelectValue(sel, value);													*
 *	getSelectIndexOfValue(sel, value);											*
 *																				*
 *	addSelectOption(sel, text[, val[, blnSelectNew]]);							*
 *	removeSelectOption(sel[, index]);											*
 *	getSelectList(sel);				//as an array...							*
 *																				*
 *	hide(obj, hide);															*
 *	hideElement(objName, hide);													*
 *																				*
 *	selectFindSimilar(select, find);											*
 *	binaryTextSearch(arr, find);												*
 *	binaryTextSearch2(arr, find[, property]);									*
 *																				*
 *	floatElement(elem, layer);													*
 *																				*
 *	Array.indexOf = function(find[, blnCaseInsensitive]);						*
 *	Array.insert = function(text[, index]);										*
 *	Array.uniqueInsert = function(text[, blnCaseInsensitive[, index]]);			*
 *	Array.remove = function(index);												*
 *																				*
 *	loadScript(url);															*
 *	appendScript(src);															*
 *	loadStyle(url);																*
 *																				*
 *	getAbsolutePosition(obj);													*
 *	getMouseXY(event);															*
 *	getPageScrollXY();															*
 *																				*
 *  *1 - requires file 'saveFile.jsp'											*
 *																				*
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

//TODO: update function list
//TODO: change timer functions into a class (and move to timer.js)

//Global internal variables (should not be accessed directly)
var jsModalWindow = new Object();		//TODO: convert this into a class
jsModalWindow.child = null;
jsModalWindow.parent = null;
jsModalWindow.oldFocus = null;
jsModalWindow.oldClick = null;
jsModalWindow.oldDClick = null;
jsModalWindow.callback = null;
jsModalWindow.returnVal = null;


//Forces the url to be saved on the client computer ('save as' window will always open)
//strURL may contain a query string if desired
function saveURL(strFilename, strURL) {
	window.location.href = "../common/saveFile.jsp?filename=" +
			encodeURIComponent(strFilename) + "&url=" + encodeURIComponent(strURL);
}



//Opens a new centered window
function centeredPopup(url, width, height, name, blnScrollbars, blnDebug) {
	scrollbars = (blnScrollbars==true) ? "yes" : "no";
	
	if (blnDebug==true)
		newWindow = window.open(url, name, "width="+width+",height="+height+",scrollbars="+scrollbars+",resizable=yes,status=yes");
	else
		newWindow = window.open(url, name, "width="+width+",height="+height+",scrollbars="+scrollbars+",resizable=no");

	//Center and focus window
	var centerX = (self.screen.availWidth-width)/2;
	var centerY = (self.screen.availHeight-height)/2;
 	newWindow.moveTo(centerX,centerY);
	newWindow.focus();
	return newWindow;
}

//moves the window to the middle of the screen, based on the supplied width & height
//If the window is LARGER than the screen, it will move it to 0,0
function centerMainWindow(width, height) {
	var left = (screen.width - width) /2;
	var top = (screen.height - height) /2;

	window.moveTo( Math.max(left, 0), Math.max(top, 0) );
}

function resizeAndCenter(width, height) {
	setWindowSize(width, height);
	centerMainWindow(width, height);
}


//Center and focus a modal-sytle window.  Unlike modalPopup, this window can be refreshed.
//NOTE: if callback is null, it won't be used.
//To Use:
//	1- define a callback function: function callback(returnValue) {...}
//  2- call dialogbox: dialogbox("url", "callback", ...);
//  3- set window.returnValue in the child url.  ***
//returns a pointer to the popup window
//*** if using IE, and the child window will be refreshed, use the following:
//			window.opener.jsModalWindow.returnVal = <return value>;
function dialogbox(url, callbackName, width, height, name, blnScrollbars, blnDebug) {
	scrollbars = (blnScrollbars==true) ? "yes" : "no";

	if (blnDebug==true)
		var dialog = window.open(url, name, "width="+width+",height="+height+",scrollbars="+scrollbars+",resizable=yes,status=yes");
	else
		var dialog = window.open(url, name, "width="+width+",height="+height+",scrollbars="+scrollbars+",resizable=no");
	

	//Center and focus window
	var centerX = (self.screen.availWidth-width)/2;
	var centerY = (self.screen.availHeight-height)/2;
 	dialog.moveTo(centerX,centerY);
	dialog.blur();

	jsModalWindow.parent = window;
	jsModalWindow.child = dialog;

	//--Make the dialog box modal--//
	jsModalWindow.oldFocus = window.onfocus;
	jsModalWindow.oldClick = window.onfocus;
	jsModalWindow.oldDClick = window.onfocus;
	window.onfocus = _modal_onfocus_;
	if (document.selection) { //IE only
		window.document.onclick = _modal_onfocus_clicked_;
		window.document.ondblclick = _modal_onfocus_clicked_;
	}
	//-----------------------------//
	jsModalWindow.callback = callbackName;

	dialog.focus();
	return dialog;
}

//internal (dialogbox)
function _modal_onfocus_() {
	if (jsModalWindow.child!=null) {
		if (jsModalWindow.child.closed) {
			var parent = jsModalWindow.parent;
			_modal_done_();
			parent.focus();8/07/07
		} else {
			jsModalWindow.child.blur();
			jsModalWindow.child.focus();
		}
	}
}
//internal (dialogbox)
function _modal_onfocus_clicked_() {
	_modal_onfocus_();
	jsModalWindow.parent.document.selection.empty();	//IE only
}
//internal (dialogbox)
function _modal_done_() {
	jsModalWindow.child = null;
	jsModalWindow.parent.onfocus = jsModalWindow.oldFocus;
	jsModalWindow.parent.document.onclick = jsModalWindow.oldClick;
	jsModalWindow.parent.document.ondblclick = jsModalWindow.oldDClick;
	
	if (jsModalWindow.callback!=null)
		eval("jsModalWindow.parent."+jsModalWindow.callback+"(jsModalWindow.returnVal);");

	jsModalWindow.returnVal = null;
	jsModalWindow.parent = null;
	jsModalWindow.callback = null;
}



//Adds code to the specified function -- new code is executed BEFORE the original code.
//Example:
//  extendFunction(document.Form[0], "submit", function() {
//		...  //new code here
//  });
function extendFunction(element, functionName, newFunction) {
	oldFunction = eval("element."+functionName);

	if (oldFunction == undefined || oldFunction == null) {  //Replace
		eval("element."+functionName+" = newFunction");
	} else {	//Extend
		var newName = functionName+((new Date()).getTime()%10000000);
		eval("element."+newName+"_1 = element."+functionName);
		eval("element."+newName+"_2 = "+newFunction);

		eval("element."+functionName+" = function() { this."+newName+"_2(); this."+newName+"_1(); }");
	}
}

//Returns the index of the Radio object with .value=strName
function getRadioIndexByName(rad, strName) {
	var index=-1;
	for (var x=0; x<rad.length && index==-1; x++) {
		if (rad[x].value == strName)
			index=x;
	}
	return index;
}

//Returns the index ratio button that's been checked. (-1 if none);
function getCheckedRadioIndex(rad) {
	var index=-1;
	for (var x=0; x<rad.length && index==-1; x++) {
		if (rad[x].checked)
			index=x;
	}
	return index;
}

//Returns the VALUE ratio button that's been checked. (null if none);
function getCheckedRadioValue(rad) {
	var index = getCheckedRadioIndex(rad);
	if (index!=-1) {
		return rad[index].value
	} else {
		return null;
	}
}


//Returns the value of the selected item in the Select object
//Optionally return an attribute other than 'value'
function getSelectValue(sel, optionAttribute) {
	var index = sel.options.selectedIndex;
	if (optionAttribute==null)
		optionAttribute = "value";
	if (index==-1) 
		return null;
	else {
		return eval("sel.options[index]."+optionAttribute);
	}
}
//returns the selected index for the Select object
function getSelectIndex(sel) {
	if (typeof(sel)=="undefined")
		return -1;
	else
		return sel.options.selectedIndex;
}

//Like seletItem.selectedIndex, but instead changes selection by value
//Sets the select value to the index where value=value, -1 (no selection) if value is not found
//uses getSelectIndexOfValue()
function selectItem(sel, value) {
	var index = getSelectIndexOfValue(sel, value);
	sel.selectedIndex = index;
}


//Returns the index of the Select option that has .value=value.  -1 if not found
function getSelectIndexOfValue(sel, value) {
	var index=-1;
	for (var x=0; x<sel.options.length && index==-1; x++) {
		if (sel.options[x].value == value)
			index=x;
	}
	return index;
}


/*F'n:	hideElement
  Desc:	Hide/show elements, based on the id attribute.
  Compat: Compatible with IE, Netscape* & Mozilla  (*not tested)
  Usage:  ishidden = hideElement(objectName, true|false|"toggle"|"get");
  Notes:  use html attribute "style='display:none;'" to start as hidden.
			 Use this to simplify the hide( element("elementName"), true|false);
*/
function hideElement(objName, hideElem) {
	return hide( element(objName), hideElem );
}

/*F'n:	hide
  Desc:	Hide/show elements.
  Compat: Compatible with IE, Netscape* & Mozilla  (*not tested)
  Usage:  ishidden = hide(object, true|false|"toggle"|"get");
  Notes:  use html attribute "style='display:none;'" to start as hidden.
*/
function hide(obj, hide) {
	if (obj==null)
		return false;

	var style = (gBrowserType=="netscape") ? obj : obj.style;

	//Determine what state to set the object
	if (hide=="toggle" || hide=="get") {
		state = style.display;
		if (hide=="toggle") 
			state = (state=="none")?"":"none";
	} else {
		var state = (hide==null||hide==true)?"none":"";
	}

	//Change the state of the object
	if (hide!="get")
		style.display = state;

	return (state=="none");  //returns current state
}


/*F'n:	addSelectOption
  Desc:	add a new option to a select box.
  Compat: Compatible with IE, Netscape* & Mozilla*  (*not tested)
*/
function addSelectOption(sel, text, val, blnSelectNew) {
	if (typeof(val)=="undefined") val = text;
	with(sel) {
		var index = options.length;
		options[index] = new Option(text, val);
		if (blnSelectNew!=false)
			options[index].selected = true;
		return index;
	}
}

/*F'n:	removeSelectOption
  Desc:	removes an existing option from a select box, by index.
  Return: false if index does not exist.
  Compat: Compatible with IE, Netscape* & Mozilla*  (*not tested)
  Notes:  If index is not supplied, the currently selected option is removed
*/
function removeSelectOption(sel, index) {
	with(sel) {
		if (typeof(index) != "number") index = options.selectedIndex;
		if (index> -1 && index < options.length) {
			options[index] = null;
			return true;
		}
	}
	return false;
}

//TODO: allow for dynamically choosing .value (default) or .innerHTML
function getSelectList(sel) {
	with(sel) {
		if (options.length==0)
			return Array(0);
		var list = new Array(options.length);
		for (var x=0; x<options.length; x++) {
			list[x] = options[x].value;
		}
		return list;
	}
}


/*F'n:	selectFindSimilar
  Desc:	Finds and selects the closest matching Select/Option list item.
  Return: true if an exact match was found
  Compat: unknown
  Notes:  Comparisons are case insensitive.  Makes use of binaryTextSearch2().
*/
function selectFindSimilar(select, find) {
	var val = "";
	var index = binaryTextSearch2(select.options, find, "text");
	select.selectedIndex = index;
	
	if (index==-1)
		return false;

	return (find.toLowerCase()==select.options[index].text.toLowerCase());
}

/*F'n:	binaryTextSearch
  Desc:	Finds the closest match in the string array using a Binary Search algorithm.
  Return: the index of the matching string, or -1 if not found.
  Compat: unknown
  Notes:  Array must be in alpha-numeric order.
			 Third parameter is optional, default is case sensitive.
  Source: http://en.wikipedia.org/wiki/Binary_search
*/
function binaryTextSearch(arr, find, caseSensitive) {
	var low = 0;
	var high = arr.length-1;

	if (caseSensitive==false) {
		//-CAsE InSeNSiTIvE SEARCH-
		find = find.toLowerCase();
		while ( low <= high ) {
			var p = Math.floor( low+((high-low)/2) );		//position to test
			var elem = arr[p].toLowerCase();
			if ( elem > find ) {
				high = p-1;
			} else if ( elem < find ) {
				low = p + 1;
			} else {	//exact match!
				return p;
			}
		}
	} else {		//code is nearly duplicated for maximum processing speed. (TODO: analyse if this is relivant)
		//-CASE SENSITIVE SEARCH-
		while ( low <= high ) {
			var p = Math.floor( low+((high-low)/2) );		//position to test
			var elem = arr[p];
			if ( elem > find ) {
				high = p-1;
			} else if ( elem < find ) {
				low = p + 1;
			} else {	//exact match!
				return p;
			}
		}
	}
	return -1;
}

/*F'n:	binaryTextSearch2
  Desc:	Finds and selects the closest matching Select/Option list item.
  Return: The index of the CLOSEST match (most matching characters).  Returns -1 if no elements
			have ANY maching characters.
  Compat: unknown
  Notes:  Comparisons are case insensitive.  Array must be in alpha-numeric order.
			 property(optional) is the name of property to test against
  Source: based on binaryTextSearch (above)
*/
function binaryTextSearch2(arr, find, property) {
	find = find.toLowerCase();

	if (arr.length==0)
		return -1;

	var low = 0;
	var high = arr.length-1;

	while ( low <= high ) {
		var p = Math.floor( low+((high-low)/2) );		//Position to test

		var text = ((property) ? eval("arr[p]."+property ) : arr[p]).toLowerCase();

		if ( text > find ) {
			high = p-1;
		} else if ( text < find ) {
			low = p + 1;
		} else {	//exact match!
			return p;
		}
	}

	//No exact match was found.  Return this CLOSEST MATCH (starting at p).
	var index = p;
	
	if (index == arr.length-1) {
		var text = ((property) ? eval("arr[index]."+property ) : arr[index]).toLowerCase();
		return text.sameAs(find) == 0 ? -1 : index;
	}
	var text1 = ((property) ? eval("arr[index]."+property ) : arr[index]).toLowerCase();
	var text2 = ((property) ? eval("arr[index+1]."+property ) : arr[index+1]).toLowerCase();

	var match1 = text1.sameAs(find);
	var match2 = text2.sameAs(find);

	if (match1==0 && match2==0)
		index = -1;
	else if ( match2 > match1 )		//compare the number of matching characters
		index = index+1;

	return index;
}


/*F'n:	floatElement
  Desc:	Sets specified element to be 'above' others(or below) by setting zIndex
  Compat: Compatible with IE, Netscape* & Mozilla*  (*not tested)
  Notes:  Setting layer to 'false' (boolean) will restore it to normal
*/
function floatElement(elem, layer) {
	if (layer==false) { //restore
		elem.style.zIndex = 0;
		elem.style.position = "static";
	} else if ( typeof(layer)=="number" ) {
		elem.style.zIndex = layer;
		elem.style.position = "relative";
	} else {
		alert("floatElement: second parameter must be Number or Boolean(false).");
	}
}



Array.prototype.indexOf = function(find, blnCaseInsensitive) {
	var index = -1;

	if (blnCaseInsensitive==true) {

		find = find.toLowerCase();
		for (var x=0; x<this.length && index==-1; x++) {
			if ( find == this[x].toLowerCase() )
				index = x;
		}

	} else {
		for (var x=0; x<this.length && index==-1; x++) {
			if (find == this[x])
				index = x;
		}
	}
	return index;
};


//if index is not supplied, text is added as last element
Array.prototype.insert = function(text, index) {
	if (!index || index > this.length) {
		this[this.length] = text;		//append
	} else {
		this.splice(index, 0, text);	//insert (delete 0 records)
	}
};

Array.prototype.uniqueInsert = function(text, blnCaseInsensitive, index) {
	var addText = (this.indexOf(text, blnCaseInsensitive)==-1);

	if ( addText )
		this.insert(text, index);

	return addText;
};

Array.prototype.remove = function(index) {
	this.splice(index, 1);
};

function getAbsolutePosition(obj) {
	var x = 0;
	var y = 0;
	while (obj.offsetParent) {
		x+= obj.offsetLeft;
		y+= obj.offsetTop;
		obj = obj.offsetParent;
	}
	return {left:x, top:y};
}

function getMouseXY(e) {
	var posx = 0;
	var posy = 0;
	if (!e) var e = window.event;
	/*if (e.pageX || e.pageY) {
		posx = e.pageX;
		posy = e.pageY;
	} else if (e.clientX || e.clientY) {
		posx = e.clientX + document.body.scrollLeft
			+ document.documentElement.scrollLeft;
		posy = e.clientY + document.body.scrollTop
			+ document.documentElement.scrollTop;
	}*/
	posx = e.screenX;
	posy = e.screenY;
	return {x:posx, y:posy};
}

function getPageScrollXY() {
	var scrOfX = 0, scrOfY = 0;
	if( typeof( window.pageYOffset ) == 'number' ) {
		//Netscape compliant
		scrOfY = window.pageYOffset;
		scrOfX = window.pageXOffset;
	} else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
		//DOM compliant
		scrOfY = document.body.scrollTop;
		scrOfX = document.body.scrollLeft;
	} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
		//IE6 standards compliant mode
		scrOfY = document.documentElement.scrollTop;
		scrOfX = document.documentElement.scrollLeft;
	}
	return {x:scrOfX, y:scrOfY};
}