//AJAX methods 
function GetXmlHttpObject(handler)
{ 
var objXmlHttp=null;

if (navigator.userAgent.indexOf("MSIE")>=0)
{ 
var strName="Msxml2.XMLHTTP";
if (navigator.appVersion.indexOf("MSIE 5.5")>=0)
{
strName="Microsoft.XMLHTTP";
} 
try
{ 
objXmlHttp=new ActiveXObject(strName);
objXmlHttp.onreadystatechange=handler;
return objXmlHttp;
} 
catch(e)
{ 
alert("Error. Scripting for ActiveX might be disabled");
return;
} 
} 
if (navigator.userAgent.indexOf("Mozilla")>=0)
{
objXmlHttp=new XMLHttpRequest();
objXmlHttp.onload=handler;
objXmlHttp.onerror=handler;
return objXmlHttp;
}
} 


//Return the last full quarter, identified by number.
//Returns:  i in [1..4]
//Test block:
/*
	var arytest = [];
	for(var i=0, j=12; i<j; i++){
		arytest.push(String(i) + ' ' + new Date(2006,i).getLastFullQuarter());
	}
	alert(arytest.join('\n'));
*/
if(! Date.prototype.getLastFullQuarter){
    Date.prototype.getLastFullQuarter = function(){
        var BeginningOfThisMonth = (new Date(this.getYear(), this.getMonth())).valueOf();
        var PrevMonth = (new Date(BeginningOfThisMonth - 1)).getMonth();
        var penultimateval = (PrevMonth+2)/3 - ((PrevMonth+2)/3 % 1);  //Remove mantissa
        return penultimateval >= 1 ? penultimateval : 4 ;
    }
}

function monthName(monthnumber)
{
var months = new Array(13);
months[1] = "January";
months[2] = "February";
months[3] = "March";
months[4] = "April";
months[5] = "May";
months[6] = "June";
months[7] = "July";
months[8] = "August";
months[9] = "September";
months[10] = "October";
months[11] = "November";
months[12] = "December";
var dateString = months[monthnumber];
return dateString;
} //

function SetDateDefaults(datetype){
//function used in defaultformvals.js that returns from/to month/year from System Table
	fromdata=System.XMLDocument.selectSingleNode("//z:row[@Name='" + datetype + "']");
	result=fromdata.getAttribute('Value');
	return result;
}

function CurrentYear(){
//function used return the current year
  now = new Date();
  result = now.getFullYear();
	return result;
}

//getDataDate(chrSrc, strField):  Retrieves the MinFromMonth, MaxFromYear, etc from 
//DataSources.xml for a particular data source (identified by chrSrc, the 
//single-character ID).  The function expects the name of one of the four date 
//fields in DataSource.xml as its second argument.
function getDataDate(chrSrc, strField){
	//Load XML
	var objDataSources = new ActiveXObject("microsoft.xmldom");
	objDataSources.async = false;
	var bLoadSuccess = objDataSources.load("/metadata/DataSource.xml");
	
	if(!bLoadSuccess){
		alert("There was a problem loading DataSource.xml.");
	}
	//Retrieve the attribute via generating an XPath expression
	return objDataSources.selectSingleNode("/xml/rs:data/z:row[@DataSource='" + chrSrc + "']").getAttribute(strField);
}


//This function runs on Analyzer startup.  It populates the available time
//frame text for all systems.  For systems with multiple data sources, it also
//calls the system's populator function for that additional information.
//Finally, for all systems, it populates the Select boxes for year querying.
function TimeFrameText(){
	//Count the number of data sources, so we know if we have to display several.
    //Displaying several is also dependent on having an element to display in (e.g. OMHAS has two data sources, but they're so well in sync that it's non-sensical to display them both).
	var minYr, maxYr;
	var chrEarly, chrLate;
	var numSources;
	var xmldatsrc = new ActiveXObject("Microsoft.XMLDOM");
	xmldatsrc.async = false;
	var bLoadSuccess = xmldatsrc.load("metadata/DataSource.xml");
	if(bLoadSuccess){
		var minDate, maxDate;
		var nodes = xmldatsrc.selectNodes("//z:row");
		numSources = nodes.length;
		chrEarly = nodes[0].getAttribute("DataSource");
		chrLate = chrEarly;
		
		//Get date extremities, by returning earliest datasource beginning and latest datasource end (by id char)
		var tmpDate;
//		alert(numSources);
		for (var i=1; i<numSources; i++){
			minDate = getDataDate(chrEarly,'MinFromYear') + "" + leadingZero(getDataDate(chrEarly,'MinFromMonth')) + "01";
			maxDate = getDataDate(chrLate,'MaxFromYear')  + "" + leadingZero(getDataDate(chrLate, 'MaxFromMonth')) + "01";
    
			tmpDate = nodes[i].getAttribute("MinFromYear") + "" + leadingZero(nodes[i].getAttribute("MinFromMonth")) + "01";
			chrEarly = dateDiff(minDate, tmpDate, "m") < 0 ? nodes[i].getAttribute("DataSource") : chrEarly;
			tmpDate = nodes[i].getAttribute("MaxFromYear") + "" + leadingZero(nodes[i].getAttribute("MaxFromMonth")) + "01";
			chrLate  = dateDiff(tmpDate, maxDate, "m") < 0 ? nodes[i].getAttribute("DataSource") : chrLate;
			
//			alert("Iteration " + i + ";\nEarly: " + chrEarly + "\nLate: " + chrLate + " after dateDiff from " + tmpDate + " to " + maxDate + ":" + dateDiff(tmpDate, maxDate, "m"));
		}
	} else {
		numSources=1;
		chrEarly = chrDispDataSrcDate;
		chrLate  = chrDispDataSrcDate;
	}
	minYr = getDataDate(chrEarly,'MinFromYear')-0;
	maxYr = getDataDate(chrLate,'MaxFromYear')-0;
    
	timeframe.innerHTML=	"(data available " + monthName(getDataDate(chrEarly,'MinFromMonth')) + ", " + getDataDate(chrEarly,'MinFromYear') +
				" to " + monthName(getDataDate(chrLate,'MaxFromMonth')) + ", " + getDataDate(chrLate,'MaxFromYear') + ")"
			
	if(document.getElementById('ExtractDates') && numSources > 1){
		timeframe.innerHTML += "<span onclick=\"extractToolTip('ExtractDates')\" style='font-size=8pt; cursor: help'>&nbsp;(click here for dates of other sources)</span>";
		
		//While we have the XMLDOM, put it to further work by populating that golden box in multi-source systems
		GiveAddlAvailDates(xmldatsrc);
	}
    
	for(i=0;i<=(maxYr-minYr);i++){
		postData.cboFrom_Year.options[i] = new Option(i+minYr, i+minYr);
		postData.cboTo_Year.options[i] = new Option(i+minYr, i+minYr);
        if(postData.cboSingleDate_Year) postData.cboSingleDate_Year.options[i] = new Option(i+minYr, i+minYr);
	}
}

//Here, this function is a no-op; systems with multiple data sources overwrite this function to populate their dates-available box.
function GiveAddlAvailDates(xmlhandle){
}

function extractToolTip(helpid)
{
window.event.cancelBubble = true;

var h=document.getElementById(helpid).style.height;
h=h.substring(0,document.getElementById(helpid).style.height.length-2);
var w=document.getElementById(helpid).style.width;
w=w.substring(0,document.getElementById(helpid).style.width.length-2);
var lefter = event.screenX+0;
var topper = event.screenY+20;

    oPopup.document.body.innerHTML = document.getElementById(helpid).outerHTML;
    oPopup.show(lefter,topper, w, h);

}


function TrimString(sInString) {
  sInString = sInString.replace( /^\s+/g, "" );// strip leading
  return sInString.replace( /\s+$/g, "" );// strip trailing
}


function dateDiff(dateFrom,dateTo, dateint) {
    var dateLast = new Date(dateTo.substring(0,4),
                            dateTo.substring(4,6)-1,
                            dateTo.substring(6,8));

    var yearLast = y2k(dateLast.getYear());
    var monthLast = dateLast.getMonth();
    var dateLast = dateLast.getDate();

        var dateFrst = new Date(dateFrom.substring(0,4),
                            dateFrom.substring(4,6)-1,
                            dateFrom.substring(6,8));

    var yearFrst = y2k(dateFrst.getYear());
    var monthFrst = dateFrst.getMonth();
    var dateFrst = dateFrst.getDate();

    yearDiff = yearLast - yearFrst;

    if (monthLast >= monthFrst)
        var monthDiff = monthLast - monthFrst;
    else {
        yearDiff--;
        var monthDiff = 12 + monthLast -monthFrst;
    }

switch (dateint){
	case "m":
		return eval((yearDiff*12) + monthDiff);
		break;
	case "q": 
		return eval((yearDiff*4) + Math.floor(monthDiff/3));
		break;				
	case "y":
		return yearDiff;
		break;	
}
   
}

function y2k(number) { return (number < 1000) ? number + 1900 : number; }


function leadingZero(nr)
{
	if (nr < 10) nr = "0" + nr;
	return nr;
}

function MaximizeMe()
  {   if (document.body.clientWidth < 820 || document.body.clientHeight < 750) {
      if (document.body.clientWidth < 820) {
        w = Math.min(screen.availWidth,850) - document.body.clientWidth;
      }
      if (document.body.clientHeight < 750) {
        h = Math.min(screen.availHeight,750) - document.body.clientHeight;
      }
    //  alert (w);
    //  alert (h);
    //  alert (screen.availWidth);
//      if (screen.availWidth < 850 || screen.availHeight < 750)  //Original values in this if clause commented out, set to match the above if clauses.  AJN 20050421
      if (document.body.clientWidth < 820 || document.body.clientHeight < 750)
        { 
            //This code has been commented out until the if clauses above are synchronized.
            //resizeBy can not be called until w and h are set; those two aren't set until BOTH if clauses above evaluate to true, but this current if clause fires if EITHER of the above clauses is true (usually window height).
          //window.moveTo(0,0);
          //window.resizeBy(w,h);
        }
    }
     
  }
//list view script

function getNode(){
var nodestr=event.srcElement.src;

if (nodestr.indexOf("lv_minus")>0){
	openclose="N"
}
if (nodestr.indexOf("lv_plus")>0){
	openclose="Y"
}
thelist="";
for(var i=0; i < numlist.length; i++)
{

var plsmns=eval("document.getElementById('lv_grp_nav_" + numlist[i] + "').childNodes(0).src");


	if (numlist[i] != event.srcElement.parentElement.getAttribute('name') && plsmns.indexOf('lv_minus')>0){
	
		thelist=thelist + numlist[i] + "_";
	}	
}

window.location.href= "layout.asp?page=" + curPage + "&sNode=S&LV=" + event.srcElement.parentElement.getAttribute('name') + "," + openclose + ","+thelist;

}
 

//CountSelections:  Returns the number of selected options in the multi-select element selMult.
function CountSelections(selMult){
	var counter=0;
	
	if(selMult.getAttribute("multiple")==true || selMult.getAttribute("multiple")=="multiple"){
		for(var i=0; i<selMult.options.length; i++){
			if (selMult.options[i].selected) counter++;
		}
	} else {
		if (selMult.options.length>0) counter=1;
	}
	
	return counter;
}

//SetSelection:  Sets the option with value 'val' in the first named select element 'sel' to selected.  If 'val' isn't found, this function returns false; otherwise, returns true.
function SetSelection(sel, val){
	var retval = false;
	var el = document.getElementsByName(sel)[0];
	for(var i=0; i<el.options.length; i++){
		if(el.options[i].value == val){
            el.options[i].selected = true;
            retval = true;
        }
	}
	return retval;
}

//isIn:  Returns true iff varval is in the array varlist
function isIn(varval, varlist){
	for(var i=0; i<varlist.length; i++){
		if(varval==varlist[i]) return true;
	}
	return false;
}

//GetRadioVal:  Based on code for GetOrgLevel in tabviewscripts
function GetRadioVal(strRadName)
{
	var retVal = "";
	var aryRads = document.getElementsByName(strRadName);
	
	for (var i = 0; i < aryRads.length; i++) {
	    if (aryRads[i].checked) {
	        retVal = aryRads[i].value;
	    }
	}

	return retVal;
}

//SetRadioVal:  Sets the radio with name strRadName and value 'val' to checked.  If the radio couldn't be set, the function returns false; returns true on successful setting.
function SetRadioVal(strRadName, val){
	var aryRads = document.getElementsByName(strRadName);
	
	for (var i = 0; i < aryRads.length; i++) {
	    if (aryRads[i].value == val) {
	        aryRads[i].checked="checked";
		return true;
	    }
	}
	
	return false;
}

//Standard Report submission code//
//intResultWindowIndex allows multiple report copies to be opened (for the same report, anyway).
//To use, append ++intResultWindowIndex to the new window being created; reference the window later with its title with appendix intResultWindowIndex.
var intResultWindowIndex = 0;

//Report container.  Kept as an array so references to already-open windows can be maintained.
var newWindows = new Array();

//Creates rptWinX for storing standard report results.  X > 0.
function setNames(source) {
	//if(source.indexOf("192.168") == -1 && source.indexOf("lganweb") == -1){	
	//	source="lgan.net";	
	//}
  source="lganweb";	

	var width=(screen.availWidth||screen.width)*0.80;
	var height=(screen.availHeight||screen.height)*0.80;

    	var features =   'width=' + width + ', height=' + height +
	', menubar=1, scrollbars=1, status=0, toolbar=0, resizable=1, top=10, left=120, alwaysRaised=1';

		document.getElementsByTagName("form")[0].action="/CGIscripts/broker.exe";
        //Open a new window to contain the results;
        var strWindowSuffix = (new Date()).valueOf();
     	newWindows[intResultWindowIndex++]= window.open ('results.html', 'rptWin' + strWindowSuffix, features);
		document.getElementsByTagName("form")[0].target="rptWin" + strWindowSuffix;
		//newWindows[intResultWindowIndex].focus();
}
//Alternate name for standard report submission function:  SubmitForm
var SubmitForm = setNames;

//DropResults:  Clears all contents of the newWindows array, destroying open windows.
//Not in use yet (nor functioning).
function DropResults(){
    for(var i=newWindows.length; i>0; i--){
        if(newWindows[i].close) newWindows[i].close();
    }
    newWindows = new Array();
}

////////////////////////////////
// Begin downloaded/adapted code

    //getElementsByClass:  "It works just how you think getElementsByClass would work, except better."
    //  Function written by Dustin Diaz 
    //  http://www.dustindiaz.com/getelementsbyclass/
    function getElementsByClass(searchClass,node,tag) {
    	var classElements = new Array();
    	if ( node == null )
    		node = document;
    	if ( tag == null )
    		tag = '*';
    	var els = node.getElementsByTagName(tag);
    	var elsLen = els.length;
    	var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
    	for (i = 0, j = 0; i < elsLen; i++) {
    		if ( pattern.test(els[i].className) ) {
    			classElements[j] = els[i];
    			j++;
    		}
    	}
    	return classElements;
    }
    
    document.getElementsByClass = getElementsByClass;
    //HTMLElement.getElementsByClass = getElementsByClass;  <-- this doesn't work for some reason in IE...
    
    //addEvent functions downloaded with toolTipLib.
    function addEvent(elm, evType, fn, useCapture) {
    	if (elm.addEventListener) { 
    	elm.addEventListener(evType, fn, useCapture); 
    	return true; 
    	}
    	else if (elm.attachEvent) { 
    	var r = elm.attachEvent('on' + evType, fn); 
    	EventCache.add(elm, evType, fn);
    	return r; 
    	}
    	else {
    	elm['on' + evType] = fn;
    	}
    }
    function getEventSrc(e) {
    	if (!e) e = window.event;
    
    	if (e.originalTarget)
    	return e.originalTarget;
    	else if (e.srcElement)
    	return e.srcElement;
    }
    //addLoadEvent:  Adds a function to be called on window.onload.
    //  Function written by Simon Willison
    //  Adopted from the listing at:  http://www.dustindiaz.com/top-ten-javascript/
    function addLoadEvent(func) {
    var oldonload = window.onload;
    	if (typeof window.onload != 'function') {
    	window.onload = func;
    	} else {
    	window.onload = 
    		function() {
    		oldonload();
    		func();
    		}
    	}
    }
    var EventCache = function(){
    	var listEvents = [];
    	return {
    		listEvents : listEvents,
    	
    		add : function(node, sEventName, fHandler, bCapture){
    			listEvents.push(arguments);
    		},
    	
    		flush : function(){
    			var i, item;
    			for(i = listEvents.length - 1; i >= 0; i = i - 1){
    				item = listEvents[i];
    				
    				if(item[0].removeEventListener){
    					item[0].removeEventListener(item[1], item[2], item[3]);
    				};
    				
    				/* From this point on we need the event names to be prefixed with 'on" */
    				if(item[1].substring(0, 2) != "on"){
    					item[1] = "on" + item[1];
    				};
    				
    				if(item[0].detachEvent){
    					item[0].detachEvent(item[1], item[2]);
    				};
    				
    				item[0][item[1]] = null;
    			};
    		}
    	};
    }();
    addEvent(window,'unload',EventCache.flush, false);
    
    
    //toolTipLib
    //Originally written by Dustin Diaz ( http://www.dustindiaz.com/sweet-titles )
    //Adapted for use in the APlus system, by changing the initialization to set
    //tooltips for elements by class instead of tag name.  (getElementsByClass also
    //written by Dustin Diaz.)
    //
    //To add additional classes to the tool tip collection, type the class name into
    //the array 'tipClasses', and add their appropriate stylings to aplus/css/toolTip.css.
    //
    //NOTE:  This has been modified for QUIRKS mode functionality by using document.body instead of document.documentElement.  This needs to be revised for Standards mode when used in a standards-compliant system.
    var toolTipLib = { 
    	xCord : 0,
    	yCord : 0,
    	obj : null,
    	tipClasses : ['tooltip_Help'],
        FadeInDelay : 500,
    	attachToolTipBehavior: function() {
    		if ( !document.getElementById ||
    			!document.createElement ||
    			!document.getElementsByTagName||
                !getElementsByClass) {
    			return;
    		}
    		var i,j;
    		addEvent(document,'mousemove',toolTipLib.updateXY,false);
    		if ( document.captureEvents ) {
    				document.captureEvents(Event.MOUSEMOVE);
    		}
    		for ( i=0;i<toolTipLib.tipClasses.length;i++ ) {
    			var current = getElementsByClass(toolTipLib.tipClasses[i],document);
    			for ( j=0;j<current.length;j++ ) {
    				addEvent(current[j],'mouseover',toolTipLib.tipOver,false);
    				addEvent(current[j],'mouseout',toolTipLib.tipOut,false);
    				current[j].setAttribute('tip',current[j].title);
    				current[j].removeAttribute('title');
    			}
    		}
    	},
    	updateXY : function(e) {
    		if ( document.captureEvents ) {
    			toolTipLib.xCord = e.pageX;
    			toolTipLib.yCord = e.pageY;
    		} else if ( window.event.clientX ) {
                //document.documentElement changed to document.body because IE quirks mode fails to recognize documentElement.
    			toolTipLib.xCord = window.event.clientX+document.body.scrollLeft;
    			toolTipLib.yCord = window.event.clientY+document.body.scrollTop;
    		}
    	},
    	tipOut: function(e) {
    		if ( window.tID ) {
    			clearTimeout(tID);
    		}
    		if ( window.opacityID ) {
    			clearTimeout(opacityID);
    		}
    		var l = getEventSrc(e);
    		var div = document.getElementById('toolTip');
    		if ( div ) {
    			div.parentNode.removeChild(div);
    		}
    	},
    	tipOver : function(e) {
    		toolTipLib.obj = getEventSrc(e);
    		tID = setTimeout("toolTipLib.tipShow()",toolTipLib.FadeInDelay);
    	},
    	tipShow : function() {
    		var newDiv = document.createElement('div');
    		var scrX = Number(toolTipLib.xCord);
    		var scrY = Number(toolTipLib.yCord);
    		var tp = parseInt(scrY+15);
    		var lt = parseInt(scrX+10);
            var newLeft=0, newTop = 0;
    		newDiv.id = 'toolTip';
    		document.getElementsByTagName('body')[0].appendChild(newDiv);
    		newDiv.style.opacity = '.1';
            
            //Insert new content
            newDiv.innerHTML = document.getElementById(toolTipLib.obj.getAttribute('tooltipref')).innerHTML;
            
            //Position
            //document.documentElement changed to document.body because IE quirks mode fails to recognize documentElement.
    		if ( parseInt(document.body.clientWidth+document.body.scrollLeft) < parseInt(newDiv.offsetWidth+lt) ) {
    			newLeft = parseInt(lt-(newDiv.offsetWidth+10));
    		} else {
    			newLeft = lt;
    		}
    		if ( parseInt(document.body.clientHeight+document.body.scrollTop) < parseInt(newDiv.offsetHeight+tp) ) {
    			newTop = parseInt(tp-(newDiv.offsetHeight+10));
    		} else {
    			newTop = tp;
    		}
            if(newLeft < 0) newLeft = 0;
            if(newTop < 0) newTop = 0;
            newDiv.style.left = newLeft + 'px';
            newDiv.style.top = newTop + 'px';
            
            ////Display debugging
            //newDiv.appendChild(document.createTextNode("(lt,tp):  (" + lt + "," + tp + ")"));
            //newDiv.appendChild(document.createElement('br'));
            //newDiv.appendChild(document.createTextNode("Dimensions:  " + document.body.clientWidth + " x " + document.body.clientHeight));
            //newDiv.appendChild(document.createElement('br'));
            //newDiv.appendChild(document.createTextNode("Scroll:  " + document.body.scrollLeft + " x " + document.body.scrollTop));
            //newDiv.appendChild(document.createElement('br'));
            //newDiv.appendChild(document.createTextNode("(left,top):  (" + newDiv.style.left + "," + newDiv.style.top + ")"));
            
            //toolTipLib.DisplayObstructions(false);
    		toolTipLib.tipFade('toolTip',10);
    	},
        //tipFade:  What makes it look So Cool.
        //Needs to be passed an element's ID, because it recursively calls itself with setTimeout (which requires a string representation of the function call).
        //Fails silently if not passed an element's id.
    	tipFade: function(elId,opac) {
            var obj = document.getElementById(elId);
            if(obj){
    			var passed = parseInt(opac);
    			var newOpac = parseInt(passed+10);
    			if ( newOpac < 80 ) {
    				obj.style.opacity = '.'+newOpac;
    				obj.style.filter = "alpha(opacity:"+newOpac+")";
    				opacityID = setTimeout("toolTipLib.tipFade('" + elId + "','"+newOpac+"')" , 20);
    			}
    			else { 
    				obj.style.opacity = '.80';
    				obj.style.filter = "alpha(opacity:80)";
    			}
            }
    	}//,
        
        //Function written (by AJN), but not used.
        ////IE 6 lets SELECT elements go on top of everything, including the tooltips.  Completely obstructs.
        ////DisplayObstructions:  If passed false, hides all select elements on the page.
        ////ASSUMES that every element it hides will be there when it restores them.
        //DisplayObstructions: function(bVal){
        //    if(!bVal){
        //        //Find all select elements that are visible onscreen (including disabled ones); store.
        //        aryVisSelects = new Array();
        //        var aryTmp = document.getElementsByTagName("select");
        //        var i;
        //        for (i=0; i<aryTmp.length; i++){
        //            if(aryTmp[i].id){
        //                //alert(aryTmp[i].id);
        //                aryVisSelects.push(aryTmp[i].id);
        //            }
        //        }
        //        //Now, use aryVisSelects as a catalog for hiding things.
        //        for (i=0; i<aryVisSelects.length; i++){
        //            toolTipLib.tipFade(aryVisSelects[i],10);
        //        }
        //    } else {
        //        
        //    }
        //},
        //aryVisSelects: null
    };
    addEvent(window,'load',toolTipLib.attachToolTipBehavior,false);
    
    // document.getElementsBySelector(selector)
    // - returns an array of element objects from the current document
    //   matching the CSS selector. Selectors can contain element names, 
    //   class names and ids and can be nested. For example:
    //   
    //     elements = document.getElementsBySelect('div#main p a.external')
    //   
    //   Will return an array of all 'a' elements with 'external' in their 
    //   class attribute that are contained inside 'p' elements that are 
    //   contained inside the 'div' element which has id="main"
    //
    // New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
    // See http://www.w3.org/TR/css3-selectors/#attribute-selectors
    //
    // Version 0.4 - Simon Willison, March 25th 2003
    // -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
    // -- Opera 7 fails 
    //
    //Downloaded from:  http://simon.incutio.com/archive/2003/03/27/attributeSelectorsNowSupported
    function getAllChildren(e) {
      // Returns all children of element. Workaround required for IE5/Windows. Ugh.
      return e.all ? e.all : e.getElementsByTagName('*');
    }
    document.getElementsBySelector = function(selector) {
      // Attempt to fail gracefully in lesser browsers
      if (!document.getElementsByTagName) {
        return new Array();
      }
      // Split selector in to tokens
      var tokens = selector.split(' ');
      var currentContext = new Array(document);
      for (var i = 0; i < tokens.length; i++) {
        token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
        if (token.indexOf('#') > -1) {
          // Token is an ID selector
          var bits = token.split('#');
          var tagName = bits[0];
          var id = bits[1];
          var element = document.getElementById(id);
          if (tagName && element.nodeName.toLowerCase() != tagName) {
            // tag with that ID not found, return false
            return new Array();
          }
          // Set currentContext to contain just this element
          currentContext = new Array(element);
          continue; // Skip to next token
        }
        if (token.indexOf('.') > -1) {
          // Token contains a class selector
          var bits = token.split('.');
          var tagName = bits[0];
          var className = bits[1];
          if (!tagName) {
            tagName = '*';
          }
          // Get elements matching tag, filter them for class selector
          var found = new Array;
          var foundCount = 0;
          for (var h = 0; h < currentContext.length; h++) {
            var elements;
            if (tagName == '*') {
                elements = getAllChildren(currentContext[h]);
            } else {
                elements = currentContext[h].getElementsByTagName(tagName);
            }
            for (var j = 0; j < elements.length; j++) {
              found[foundCount++] = elements[j];
            }
          }
          currentContext = new Array;
          var currentContextIndex = 0;
          for (var k = 0; k < found.length; k++) {
            if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
              currentContext[currentContextIndex++] = found[k];
            }
          }
          continue; // Skip to next token
        }
        // Code to deal with attribute selectors
        if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
          var tagName = RegExp.$1;
          var attrName = RegExp.$2;
          var attrOperator = RegExp.$3;
          var attrValue = RegExp.$4;
          if (!tagName) {
            tagName = '*';
          }
          // Grab all of the tagName elements within current context
          var found = new Array;
          var foundCount = 0;
          for (var h = 0; h < currentContext.length; h++) {
            var elements;
            if (tagName == '*') {
                elements = getAllChildren(currentContext[h]);
            } else {
                elements = currentContext[h].getElementsByTagName(tagName);
            }
            for (var j = 0; j < elements.length; j++) {
              found[foundCount++] = elements[j];
            }
          }
          currentContext = new Array;
          var currentContextIndex = 0;
          var checkFunction; // This function will be used to filter the elements
          switch (attrOperator) {
            case '=': // Equality
              checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
              break;
            case '~': // Match one of space seperated words 
              checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
              break;
            case '|': // Match start with value followed by optional hyphen
              checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
              break;
            case '^': // Match starts with value
              checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
              break;
            case '$': // Match ends with value - fails with "Warning" in Opera 7
              checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
              break;
            case '*': // Match ends with value
              checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
              break;
            default :
              // Just test for existence of attribute
              checkFunction = function(e) { return e.getAttribute(attrName); };
          }
          currentContext = new Array;
          var currentContextIndex = 0;
          for (var k = 0; k < found.length; k++) {
            if (checkFunction(found[k])) {
              currentContext[currentContextIndex++] = found[k];
            }
          }
          // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
          continue; // Skip to next token
        }
        // If we get here, token is JUST an element (not a class or ID selector)
        tagName = token;
        var found = new Array;
        var foundCount = 0;
        for (var h = 0; h < currentContext.length; h++) {
          var elements = currentContext[h].getElementsByTagName(tagName);
          for (var j = 0; j < elements.length; j++) {
            found[foundCount++] = elements[j];
          }
        }
        currentContext = found;
      }
      return currentContext;
    }
    /* That revolting regular expression explained 
    /^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
      \---/  \---/\-------------/    \-------/
        |      |         |               |
        |      |         |           The value
        |      |    ~,|,^,$,* or =
        |   Attribute 
       Tag
    */

// End downloaded/adapted code
////////////////////////////////

//Documentation and testing at http://192.168.1.112/alexanalyzer/miscCode/ProgrammingNotes.xml#200603081201
function EndOfThisMonth(d){
    var retval, tmpd = new Date(d.valueOf());
    //Increment the Date object's month value.
    tmpd.setMonth(d.getMonth()+1);
    
    //Convert with .valueOf() to milliseconds.
    var dms = tmpd.valueOf();
    
    //Decrement the milliseconds
    dms--;
    
    //Create a new Date.
    retval = new Date(dms);
    
    return retval;
}


//TypedYear:  Given a date, returns the year that date is in.  The year is one of: US calendar, US state fiscal, or US federal fiscal.
//Fiscal years:  When they hit the newyear month, the year changes to the forward year.
//Pre:  date is of type Date, strYearType is in {"calendar","fiscal","fiscal-state","fiscal-federal"}
//      "fiscal" acts as "fiscal-state"
//      If the date parameter is created as a new Date(), recall that the month argument is 0-based.
//Post:  Returns 4-digit year, as an int
//Throws:  Throws an error if strYearType is not a string.  Custom error message was created because calling .toLowerCase() on a boolean variable gave an insufficiently descriptive error message.
//Documentation and testing at http://192.168.1.112/alexanalyzer/miscCode/ProgrammingNotes.xml#200602211603
function TypedYear(date, strYearType){
    if(typeof strYearType != 'string') throw new Error("Incorrect argument type passed to function TypedYear(Date, string); expecting a string, received a " + typeof strYearType + ".");
    var retval = date.getFullYear();
    var mnth = date.getMonth() + 1; //getMonth's range is [0,11]; convert for easier thinking
    switch(strYearType.toLowerCase()){
        case "fiscal":  //Fallthrough
        case "fiscal-state":
            if(mnth > 6){
                retval++;
            }
        break;
        case "fiscal-federal":
            if(mnth > 9){
                retval++;
            }
        break;
        case "calendar":  //Fallthrough
        default:
            //nop
        break;
    }
    return retval;
}

//Documentation:  http://192.168.1.112/alexanalyzer/miscCode/ProgrammingNotes.xml#200603081601  
function FirstMonthOfTypedYear(numYear, strType){
    var y=numYear,m;  //Reminder:  month is 0-based
    switch(strType){
        case "fiscal":  //Spillover
        case "fiscal-state":
            y--;
            m=6;
        break;
        case "fiscal-federal":
            y--;
            m=9;
        break;
        case "calendar":  //Spillover
        default:
            m=0;
        break;
    }
    return new Date(y,m);
}

//Documentation:  http://192.168.1.112/alexanalyzer/miscCode/ProgrammingNotes.xml#200603081601
//Potential future feature:  Go to not only last month, but last millisecond of the last month.
function LastMonthOfTypedYear(numYear, strType){
    var y=numYear,m;  //Reminder:  month is 0-based
    switch(strType){
        case "fiscal":  //Spillover
        case "fiscal-state":
            m=5;
        break;
        case "fiscal-federal":
            m=8;
        break;
        case "calendar":  //Spillover
        default:
            m=11;
        break;
    }
    return new Date(y,m);
}

//Documentation:  http://192.168.1.112/alexanalyzer/miscCode/ProgrammingNotes.xml#200603081601
function TypedYearsBetween(dateBeg, dateEnd, strYearType, bFullYearsOnly){
    var retary = new Array();

    //Build list of all years under consideration
    for(var i=TypedYear(dateBeg,strYearType); i<=TypedYear(dateEnd,strYearType); i++){
        retary.push(i);
    }
    
    //Test and trim, if requested
    if(bFullYearsOnly){
        if(retary.length > 0){
            //Existence test:  Does there exist m, m in the first typed year, where m comes before dateBeg?  If yes, remove the first year from the list.
            if(FirstMonthOfTypedYear(retary[0],strYearType).valueOf() < dateBeg.valueOf()){
                //Popping removes the last element of the array.
                retary.reverse();
                retary.pop();
                retary.reverse();
            }
            
            if(retary.length > 0){
                //Existence test:  Does there exist m, m in the last typed year, where m comes after dateEnd?  If yes, remove the last year from the list.
                if(dateEnd.valueOf() < LastMonthOfTypedYear(retary[retary.length-1],strYearType).valueOf()) retary.pop();
            }
        }
    }
    
    return retary;
}

//PopulateYearSelect:  Destructively creates the options for a select element meant for holding years.  Written for forms with time input being only a single year field.
//Pre:  sel is a select element, dateBeg and dateEnd are Date objects.
//Post:  sel's options span from the typed year of dateBeg to the typed year of dateEnd; a boundary value is included only for -complete- years ('complete' meaning first month of year and last month of year are included).
//Dependency:  TypedYear(Date, string), TypedYearsBetween
//Parameter:  bReverse - by default, the first option created will be the earliest year, the last the most current year.  bReverse=true means the most current year will be the first option.
//Parameter:  dateRange - a numeric value, a number of months.  Used with rangeFrom.
//Parameter:  rangeFrom - {beg|end}, 'end' specifies we're looking from the end of a report backwards, so we won't keep options whose selection would create a report querying data before available data.
//Optional argument:  type, default value 'calendar'
//Optional argument:  bFullyearsOnly, default value true.
function PopulateYearSelect(sel, dateBeg, dateEnd, type, dateRange, rangeFrom, bReverse, bFullYearsOnly){
    if(sel && dateBeg && dateEnd){
        if(!type){
            type = 'calendar';
        }
        //Check for inclusion of optional boolean arguments - avoids type coercion with (not)triple-equals operators.
        if(bReverse !== true && bReverse !== false){
            bReverse = true;
        }
        if(bFullYearsOnly !== true && bFullYearsOnly !== false){
            bFullYearsOnly = true;
        }
        
        sel.options.length=0;
        var aryNewOptionYears = TypedYearsBetween(dateBeg, dateEnd, type, bFullYearsOnly);
        if(bReverse) aryNewOptionYears.reverse();
        
        var bAddThisYear, tmpDate;
        //alert(FirstMonthOfTypedYear(aryNewOptionYears[0],type)+'\n'+new Date(FirstMonthOfTypedYear(aryNewOptionYears[0],type).setMonth(FirstMonthOfTypedYear(2005,type).getMonth()-dateRange)));
        for (var i=0; i<aryNewOptionYears.length; i++){
            bAddThisYear = true;
            
            //Trim years that would create a bad report (going out of bounds)
            //NOTE decrementing dateRange by 1 here to account for an off-by-one error; a calendar-year report from the end of 2004 is from the end of December 2004, to the beginning of JANUARY 2002, NOT December 2001.
            if (rangeFrom == "beg"){
                tmpDate = FirstMonthOfTypedYear(aryNewOptionYears[i],type);
                if(tmpDate.setMonth(tmpDate.getMonth()+dateRange-1) > dateEnd.valueOf()){  //Note that setMonth returns the millisec representation of the adjusted date.
                    bAddThisYear = false;
                }
            } else if(rangeFrom == "end"){
                tmpDate = LastMonthOfTypedYear(aryNewOptionYears[i],type);
                if(tmpDate.setMonth(tmpDate.getMonth()-dateRange+1) < dateBeg.valueOf()) bAddThisYear = false;
                
            }
            //For assurance on this utilization of setMonth, see http://192.168.1.112/alexanalyzer/miscCode/ProgrammingNotes.xml#200603101450 .
            
            if(bAddThisYear){
                sel.options[sel.options.length] = new Option(aryNewOptionYears[i],aryNewOptionYears[i],false,false);
            }
        }
    }
}

//ReportError:  string -> element -> boolean -> boolean, with statechange
//Meant to be overridden by specific systems' reporting methods.
//Assumes in general that there exists the element 'divErrors' which contains HTML for reporting errors.  If not, defaults to Alerting the user.
//elLoc is the location the error is reported; assumed to be block-level.
//bcleanup is an optional flag to remove the contents of the elLoc container on this iteration of the function.
function ReportError(err, elLoc, bcleanup){
    if(typeof err != 'string' && !err.toString) return false; //Unknown object type.
    var errloc = (typeof elLoc != 'undefined') ? elLoc : document.getElementById("divErrors");

    if(errloc){
        //Cleanup last round's error
        if(bcleanup) errloc.innerHTML = "";
        
        //Ensure we'll be displaying the element
        errloc.style.display = 'block';
        var dispmsg = "<p>" + err + "</p>";
        var regstalemsg;  //This variable takes care of removing the error message on a valid update.
        try{
            //Just in case we try to make an illegal regexp
            regstalemsg = new RegExp(dispmsg, 'gi');
        } catch(e) {
            //do nothing
        }
        if(regstalemsg) errloc.innerHTML = errloc.innerHTML.replace(regstalemsg, '');
        errloc.innerHTML += dispmsg;
    } else {
        //Alternate reporting?  Alert box for now.
        alert(err);
    }
    return true;
}


