/**
 * jt_utils.js - "JavaScript Toolkit" utility functions
 *
 * Copyright (c) 2005-2007 Joseph Oster, wingo.com
 */

/* GENERIC FUNCTIONS */
String.prototype.trim = function () {
 return this.replace(/^\s+/g, '').replace(/\s+$/g, '');
 }

function jt_strEmpty(st) {
 // return 'true' unless 'st' is a non-null string
 if (typeof st == 'string') return (st.trim() == '');
 else return true;
 }

function $(divName) {
 return document.getElementById(divName);
 }

function jt_ShowHideElm(elm, showIt) {
 if (elm) elm.style.visibility = (showIt) ? "visible" : "hidden";
 }

function jt_ShowNoneElm(elm, showIt, showStyle) {
 if (!showStyle) showStyle = "";
 if (elm) elm.style.display = (showIt) ? showStyle : "none";
 }

function jt_ShowHide(divName, showIt) {
 jt_ShowHideElm(document.getElementById(divName), showIt);
 }

function jt_ShowNone(divName, showIt, showStyle) {
 jt_ShowNoneElm(document.getElementById(divName), showIt, showStyle);
 }


/********** BEGIN: screen handling **********/
function jt_valPx(pixels) {
 return pixels + "px";
 }

function jt_moveTo(obj, x, y) {
 obj.style.left = jt_valPx(x);
 obj.style.top = jt_valPx(y);
 }

function jt_Point(x, y) {
 // returns a "Point" object with '.x' and '.y' properties
 this.x = x;
 this.y = y;
 }

function jt_getOffsetXY(obj, findID, point) {
 // 'findID' and 'point' are optional
 // returns 'jt_Point' object with '.x' and '.y' offsets of 'obj' relative to page
 // or relative to 'findID' if 'findID' is used and found as a parent
 if (point) { // re-use when valid; good practice when dragging
 point.x = obj.offsetLeft;
 point.y = obj.offsetTop;
 }
 else point = new jt_Point(obj.offsetLeft, obj.offsetTop);
 var parent = obj.offsetParent;
 while (parent != null) {
 if (findID && (parent.id == findID)) break;
 point.x += parent.offsetLeft;
 point.y += parent.offsetTop;
 parent = parent.offsetParent;
 }
 return point;
 }

function jt_getOffsetX(obj) {
 // returns 'x' coordinate of 'obj'
 var xPos = obj.offsetLeft;
 var parent = obj.offsetParent;
 while (parent != null) {
 xPos += parent.offsetLeft;
 parent = parent.offsetParent;
 }
 return xPos;
 }

function jt_getOffsetY(obj) {
 // returns 'y' coordinate of 'obj'
 var yPos = obj.offsetTop;
 var parent = obj.offsetParent;
 while (parent != null) {
 yPos += parent.offsetTop;
 parent = parent.offsetParent;
 }
 return yPos;
 }

function jt_currStyle(divToRead) {
 // return current (derived) CSS style object
 var cs = divToRead.style;
 if (window.getComputedStyle) cs = window.getComputedStyle(divToRead,null);
 else if (divToRead.currentStyle) cs = divToRead.currentStyle;
 return cs;
 }

function jt_width(divToRead) {
 var wide = jt_currStyle(divToRead).width;
 return (wide = 'auto') ? divToRead.offsetWidth : parseInt(wide);
 }

function jt_height(divToRead) {
 var high = jt_currStyle(divToRead).height;
 return (high = 'auto') ? divToRead.offsetHeight : parseInt(high);
 }

function jt_winW() {
 return window.innerWidth ? window.innerWidth : document.body.offsetWidth;
 }

function jt_winH() {
 return window.innerHeight ? window.innerHeight : document.body.offsetHeight;
 }

function jt_scrollTop() {
 if (window.pageYOffset) return window.pageYOffset;
 else if (document.documentElement && (document.documentElement.scrollTop > 0)) return document.documentElement.scrollTop;
 else return document.body.scrollTop;
 }
/********** END: screen handling **********/


/********** BEGIN: Event handling **********/
function jt_AddListener(obj, evType, fn) {
 if (obj.addEventListener) {
 obj.addEventListener(evType, fn, false);
 return true;
 }
 else if (obj.attachEvent) return obj.attachEvent('on' + evType, fn);
 else return false;
 }

function jt_fixE(ev) {
 var e = ev ? ev : window.event;
 return e;
 }
/********** END: Event handling **********/


/********** BEGIN: FORM handling **********/
function jt_setRadio(radioFld, val) {
 // set 'radioFld' button with value == val and return 'true' (if not disabled!)
 for (var i=0; i<radioFld.length; i++)
 if (radioFld[i].value == val)
 if (!radioFld[i].disabled) {
 radioFld[i].checked = true;
 return true;
 }
 return false;
 }

function jt_getRadio(radioFld) {
 // return value of selected 'radioFld' button
 var st = "";
 for (var i=0; i<radioFld.length; i++)
 if (radioFld[i].checked) {
 st = radioFld[i].value;
 break;
 }
 return st;
 }

function foSelected(pulldown) {
 // return value of selected item
 var st = "";
 for (var i=0; i<pulldown.options.length; i++)
 if (pulldown.options[i].selected) {
 if (pulldown.options[i].value) st = pulldown.options[i].value
 else st = pulldown.options[i].text;
 break;
 }
 return st;
 }

function foPosInList(pulldown, val) {
 // return position of 'val' in pulldown menu, -1 if not found
 if (val != "")
 for (var i=0; i<pulldown.options.length; i++) {
 var opVal = pulldown.options[i].value;
 if (opVal == "") opVal = pulldown.options[i].text;
 if (opVal == val) {
 return i;
 break;
 }
 }
 return -1;
 }

function foSetSelectVal(pulldown, val) {
 // set "SELECTED" for item in pulldown menu with 'value==val'
 var p = foPosInList(pulldown,val);
 if (p != -1) pulldown.options.selectedIndex = p;
 }

function jt_formStr(aForm) {
 var qSt = '';
 for (var i=0; i<aForm.elements.length; i++) {
 if ((aForm.elements[i].type == 'radio') || (aForm.elements[i].type == 'checkbox')) {
 if (aForm.elements[i].checked) qSt += '&' + aForm.elements[i].name + '=' + aForm.elements[i].value || 'checked';
 }
 else if (aForm.elements[i].type.indexOf('select') != -1) {
 for (var j=0; j<aForm.elements[i].options.length; j++) {
 if (aForm.elements[i].options[j].selected) qSt += '&' + aForm.elements[i].name + '=' + encodeURIComponent((aForm.elements[i].options[j].value) ? aForm.elements[i].options[j].value : aForm.elements[i].options[j].text);
 }
 }
 else if (!jt_strEmpty(aForm.elements[i].value)) qSt += '&' + aForm.elements[i].name + '=' + encodeURIComponent(aForm.elements[i].value);
 }
 return qSt;
 }
/********** END: FORM handling **********/


function jt_parseQuery(queryString) {
 // converts name/value pairs in 'queryString' to JS object
 var qObj = new Object();
 var stQuery = (queryString) ? queryString : location.search; // use 'location.search' if 'queryString' is null
 if (stQuery.indexOf("?") == 0) stQuery = stQuery.substring(1);
 if (stQuery) {
 var nvPairs = stQuery.split("&");
 for (var i=0; i < nvPairs.length; i++) {
 var posEq = nvPairs[i].indexOf("=");
 if (posEq != -1) {
 var nam = nvPairs[i].substring(0,posEq);
 if (nam.indexOf('.') == -1) qObj[nam] = nvPairs[i].substring(posEq+1);
 }
 }
 }
 return qObj;
 }

function jt_safeHTML(st) {
 // encode - same as 'u:htmlencode' tag
 if (st.length == 0) return "";
 st = st.replace(/</gi,"&lt;");
 st = st.replace(/>/gi,"&gt;");
 st = st.replace(/\"/gi,'&quot;');
 st = st.replace(/\'/gi,"&#39;");
 st = st.replace(/\\/gi,"&#92;");
 return st;
 }

function jt_unsafeHTML(st) {
 // decode - opposite of 'u:htmlencode' tag
 if (st.length == 0) return "";
 st = st.replace(/&lt;/gi,"<");
 st = st.replace(/&gt;/gi,">");
 st = st.replace(/&quot;/gi,'"');
 st = st.replace(/&#39;/gi,"'");
 st = st.replace(/&#92;/gi,"\\");
 return st;
 }

function jt_plural(qty, lbl) {
 // returns plural suffix or optional extended format if 'lbl' is present; example: qty=12, lbl="server", return value="12 servers"
 var stPlural = (qty == 1) ? "" : "s";
 if (lbl) stPlural = qty + " " + lbl + stPlural;
 return stPlural;
 }

function jt_alignCorner(elmToMove, elmAnchor, TlTrBlBr, xOffset, yOffset) {
 // aligns 'elmToMove' with 'elmAnchor' based on 2-character 'TlTrBlBr' indicating corner: 'TL' | 'TR' | 'BL' | 'BR'
 xOffset = xOffset ? xOffset : 0; // optional param
 yOffset = yOffset ? yOffset : 0; // optional param
 var anchorXY = jt_getOffsetXY(elmAnchor);
 var xxOffset = (TlTrBlBr.indexOf('R') != -1) ? elmToMove.offsetWidth - elmAnchor.offsetWidth : 0;
 var yyOffset = (TlTrBlBr.indexOf('B') != -1) ? elmToMove.offsetHeight : 0;
 jt_moveTo(elmToMove, anchorXY.x - xxOffset + xOffset, anchorXY.y - yyOffset + yOffset);
 }

function jt_divOnScrn(divOnScrn) {
 var divPos = jt_getOffsetXY(divOnScrn);
 var newX = divPos.x;
 var newY = divPos.y;
 if (divPos.x + divOnScrn.offsetWidth - document.body.scrollLeft > document.body.clientWidth) newX = document.body.scrollLeft + document.body.clientWidth - divOnScrn.offsetWidth;
 if (divPos.x < document.body.scrollLeft) newX = document.body.scrollLeft;
 if (divPos.y + divOnScrn.offsetHeight - document.body.scrollTop > document.body.clientHeight) newY = document.body.scrollTop + document.body.clientHeight - divOnScrn.offsetHeight;
 if (divPos.y < document.body.scrollTop) newY = document.body.scrollTop;
 if ((newX != divPos.x) || (newY != divPos.y)) jt_moveTo(divOnScrn, newX, newY);
 }

function jt_appendRelative(dragDIV, newParentDIV, xOffset, yOffset) {
 var dragPos = jt_getOffsetXY(dragDIV);
 var newParentPos = jt_getOffsetXY(newParentDIV);
 var xPos = dragPos.x - newParentPos.x;
 var yPos = dragPos.y - newParentPos.y;
 jt_moveTo(dragDIV, xPos + (xOffset ? xOffset : 0), yPos + (yOffset ? yOffset : 0));
 newParentDIV.appendChild(dragDIV);
 }

function jt_DelChildren(elm) {
 // remove all child nodes of 'elm' from DOM and delete them
 while (elm.hasChildNodes()) {
 var oldChild = elm.removeChild(elm.childNodes[elm.childNodes.length-1]);
 delete(oldChild);
 }
 }


// from http://www.quirksmode.org/js/detect.html
var BrowserDetect = {
 init: function () {
 this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
 this.version = this.searchVersion(navigator.userAgent) || this.searchVersion(navigator.appVersion) || "an unknown version";
 this.OS = this.searchString(this.dataOS) || "an unknown OS";
 },
 searchString: function (data) {
 for (var i=0;i<data.length;i++) {
 var dataString = data[i].string;
 var dataProp = data[i].prop;
 this.versionSearchString = data[i].versionSearch || data[i].identity;
 if (dataString) {
 if (dataString.indexOf(data[i].subString) != -1) return data[i].identity;
 }
 else if (dataProp) return data[i].identity;
 }
 },
 searchVersion: function (dataString) {
 var index = dataString.indexOf(this.versionSearchString);
 if (index == -1) return;
 return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
 },
 dataBrowser: [
 {string: navigator.vendor, subString: "Apple", identity: "Safari"},
 {prop: window.opera, identity: "Opera"},
 {string: navigator.vendor, subString: "iCab", identity: "iCab"},
 {string: navigator.vendor, subString: "KDE", identity: "Konqueror"},
 {string: navigator.userAgent, subString: "Firefox", identity: "Firefox"},
 {string: navigator.userAgent, subString: "Netscape", identity: "Netscape"}, // for newer Netscapes (6+)
 {string: navigator.userAgent, subString: "MSIE", identity: "Explorer", versionSearch: "MSIE"},
 {string: navigator.userAgent, subString: "Gecko", identity: "Mozilla", versionSearch: "rv"},
 {string: navigator.userAgent, subString: "Mozilla", identity: "Netscape", versionSearch: "Mozilla"} // for older Netscapes (4-)
 ],
 dataOS : [
 {string: navigator.platform, subString: "Win", identity: "Windows"},
 {string: navigator.platform, subString: "Mac", identity: "Mac"},
 {string: navigator.platform, subString: "Linux", identity: "Linux"}
 ]
 };
BrowserDetect.init();

var jt_FixPNG = (BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 7);

var spImg = new Image(); spImg.src = "/images/sp.gif";

function correctPNG(imgObj) {
 // IE only - must call image loads!
 if (jt_FixPNG) {
 if (typeof imgObj == 'string') imgObj = document.getElementById(imgObj);
 else if (!imgObj) {
 if (this) imgObj = this;
 else {
 var e = jt_fixE(imgObj);
 imgObj = e.target ? e.target : e.srcElement;
 }
 }
 imgObj.onload = null;
 if (imgObj.src.indexOf("sp.gif") == -1) {
 imgObj.style.width = jt_valPx(imgObj.attributes.width.value);
 imgObj.style.height = jt_valPx(imgObj.attributes.height.value);
 imgObj.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imgObj.src + "')";
 imgObj.src = spImg.src;
 }
 }
 }


function jt_boxOverlap(objectA, objectB, mode) {
 // box collision detector; returns area of overlap (if any) in pixels between DOM elements 'objectA' and 'objectB'
 // adapted from http://www.gamedev.net/reference/articles/article754.asp
 // 'mode' is optional - two special modes are offered:
 // mode='X' - test for overlap of X-coordinates only; ignore Y
 // mode='Y' - test for overlap of Y-coordinates only; ignore X
 var xyA = jt_getOffsetXY(objectA);
 var xyB = jt_getOffsetXY(objectB);

 var objAxTL = xyA.x; // AX1 - xTopLeft
 var objAyTL = xyA.y; // AY1 - yTopLeft
 var objAxBR = xyA.x + objectA.offsetWidth; // AX2 - xBottomRight
 var objAyBR = xyA.y + objectA.offsetHeight; // AY2 - yBottomRight

 var objBxTL = xyB.x; // BX1
 var objByTL = xyB.y; // BY1
 var objBxBR = xyB.x + objectB.offsetWidth; // BX2
 var objByBR = xyB.y + objectB.offsetHeight; // BY2
/*
alert("objAxTL=" + objAxTL + " - objAyTL=" + objAyTL + " - objAxBR=" + objAxBR + " - objAyBR=" + objAyBR + "\n" +
 "objBxTL=" + objBxTL + " - objByTL=" + objByTL + " - objBxBR=" + objBxBR + " - objByBR=" + objByBR);

reject the following conditions:
AX2<BX1
AY2<BY1
BX2<AX1
BY2<AY1
*/
 if (mode != 'Y') {
 if (objAxBR < objBxTL) return -1;
 if (objBxBR < objAxTL) return -1;
 }
 if (mode != 'X') {
 if (objAyBR < objByTL) return -1;
 if (objByBR < objAyTL) return -1;
 }
/*
If AX1<BX1 then CX1=BX1 and CX2=AX2, otherwise, CX1=AX1 and CX2=BX2
If AY1<BY1 then CY1=BY1 and CY2=AY2, otherwise, CY1=AY1 and CY2=BY2
*/
 var objCxTL;
 var objCyTL;
 var objCxBR;
 var objCyBR;
 if (objAxTL < objBxTL) {
 objCxTL = objBxTL;
 objCxBR = objAxBR;
 }
 else {
 objCxTL = objAxTL;
 objCxBR = objBxBR;
 }
 if (objAyTL < objByTL) {
 objCyTL = objByTL;
 objCyBR = objAyBR;
 }
 else {
 objCyTL = objAyTL;
 objCyBR = objByBR;
 }
 var olX = objCxBR - objCxTL;
 var olY = objCyBR - objCyTL;
 if (mode == 'X') return olX;
 else if (mode == 'Y') return olY;
 else return olX * olY;
 } // jt_boxOverlap


/********* BEGIN: jt_AnimCalc *********/
function jt_AnimCalc(callFunc, specs) {
 this._callFunc = callFunc;
 this.ranges = {};
 this._specs = {};
 this.setSpecs(jt_AnimCalc.defaultSpecs);
 if (specs) this.setSpecs(specs);
 }

jt_AnimCalc.prototype.setRange = function(name, from, to) {
 this.ranges[name] = {'from':from, 'to':to, 'delta':to - from};
 }

jt_AnimCalc.prototype.flipRange = function() {
 for(var prop in this.ranges) {
 var temp = this.ranges[prop].from;
 this.ranges[prop].from = this.ranges[prop].to;
 this.ranges[prop].to = temp;
 this.ranges[prop].delta = this.ranges[prop].to - this.ranges[prop].from;
 }
 }

jt_AnimCalc.prototype.start = function(callDone) {
 clearInterval(this.timer);
 this._callDone = callDone;
 this._callFunc(this.step = this._frac = 0);
 var _this = this;
 this.timer = setInterval(function() {
 _this.step++;
 _this._frac = (1 - Math.cos(_this.step * _this.piStep)) / 2;
 _this._callFunc(_this.step);
 if (_this.step == _this._specs.numSteps) {
 clearInterval(_this.timer);
 if (_this._callDone) _this._callDone();
 }
 },
 _this.interval);
 }

jt_AnimCalc.prototype.value = function(name) {
 return this.ranges[name].from + Math.round(this._frac * this.ranges[name].delta);
 }

jt_AnimCalc.prototype.setSpecs = function(specs) {
 for(var prop in specs) this._specs[prop] = specs[prop];
 this.interval = Math.round(this._specs.elapsed / this._specs.numSteps);
 this.piStep = Math.PI / this._specs.numSteps;
 }

jt_AnimCalc.defaultSpecs = {'numSteps':10, 'elapsed':333}
/********* END: jt_AnimCalc *********/


/********** BEGIN: trace feature **********/
var traceAreaDIV;
var traceAreaX = 100;
var traceAreaY = 50;
var traceAreaMax = 70;
var traceCount = 0;

function clearTrace() {
 traceAreaDIV.style.color = "#a9a9a9";
 traceAreaDIV.style.textAlign = "left";
 traceAreaDIV.innerHTML = '<a href="javascript:clearTrace()">clear trace</a><br>';
 }

function traceTxt(txt) {
 if (!traceAreaDIV) {
 traceAreaDIV = document.createElement("div");
 traceAreaDIV.style.position = "absolute";
 jt_moveTo(traceAreaDIV, traceAreaX, traceAreaY);
 document.body.appendChild(traceAreaDIV);
 }
 if (traceCount % traceAreaMax == 0) clearTrace();
 traceAreaDIV.innerHTML += txt + "<br>";
 traceCount++;
 }


var qParams = new jt_parseQuery(); // for session ID and trace

function traceMsg(msg) {
 if (qParams.trace == 1) traceTxt(msg);
 }

function traceXML(req, locMsg) {
 if (qParams.traceXML == 1) traceTxt(locMsg + "<br>" + jt_safeHTML(req.responseText).replace(/\n/gi,"<br>"));
 }
/********** END: trace feature **********/

function objToString(anObj, sep) {
 // convert any JS object to string of name:value pairs separated by optional 'sep'
 // NOTE: pass '<br />' for 'sep' to display as HTML
 if (!sep) sep = ' ][ '; // good for 'alert()' msgs
 var st = "";
 for(var prop in anObj) {
 if ((prop.charAt(0) == '$') || (prop == 'domConfig') || ((typeof anObj[prop]) == 'function')) continue;
 if (st != "") st += sep;
 st += prop + ':' + anObj[prop];
 }
 return st;
 }

