
//add a trim function to JS Strings
//String.prototype.trim=function(){return this.replace(/^ +| +$/g,'');};
//trim: more aggressive, takes more forms of whitespace
String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,'');};

//add a reverse function to JS Strings
String.prototype.reverse=function(){return this.split('').reverse().join('');};

//add a function to JS Strings to remove the px from style values
String.prototype.dpx=function(){return this.replace(/px$/,'');}



//root object for the AJAS namespace.
//stuff that lends itself to having such short names that they ought not be in
//the depths of a namespace, are kept shallow, here, with underscored names
var ajas={};

//a flag to cache whether we are in an IE browser.
//set by conditional comments
ajas._bIE=false;
document.write('<!--[if IE]><script type="text/javascript">ajas._bIE=true;</sc'
	+'ript><![endif]-->');


//shortname alias for the document.getElementById() function.
ajas._e=function(s){return document.getElementById(s);};


//namespace for JS event related code.
ajas.event={};

//get target/srcElement of event
ajas.event.o=function(event){
	event=event||window.event;
	return event.target||event.srcElement;
};

// stop an event from propagating up
ajas.event.cancelBubble=function(event) {
	event=event||window.event;
	if (event) {
		event.returnValue = false;
		event.cancelBubble = true;
		if(event.preventDefault)event.preventDefault();
		if(event.stopPropagation)event.stopPropagation();
	}
	return false;
};

//call to block return-key events
ajas.event.catchReturn=function(event) {
	event=event||window.event;
	var key = event.keyCode?event.keyCode:event.which;
	if (key==13){
        (event.target||event.srcElement).blur();
		ajas.event.cancelBubble(event);
		return false;
	}
	return true;
};

//get coordinates of a mouse event
ajas.event.mouseCoords=function(event){
	event=event||window.event;
	if(event.pageX || event.pageY){
		return {x:event.pageX, y:event.pageY};
	}
	return {x:event.clientX+document.body.scrollLeft-document.body.clientLeft,
		y:event.clientY+document.body.scrollTop-document.body.clientTop};
};

//test keycode and shift key to determine whether CAPS LOCK is active
ajas.event.testCapsLock=function(event) {
	event=event||window.event;
	var key=event.which?event.which:(event.keyCode?event.keyCode:
		(event.charCode?event.charCode:0));
	var bShift=event.shiftKey||(event.modifiers&&(event.modifiers&4)); 
	return (key>=65&&key<=90&&!bShift)||(key>=97&&key<=122&&bShift);
};

//for functions related to html forms
ajas.form={};

//take the values from a form, and return an array
ajas.form.getValues=function(oForm){
	var aVals=[];
	for(var i=0;i<oForm.elements.length;i++) {
		if (oForm.elements[i].disabled==false)
		switch(oForm.elements[i].type) {
		case "reset":
			break;
		case "checkbox":
		case "radio":
			if (oForm.elements[i].checked) {
				aVals[oForm.elements[i].name]=oForm.elements[i].value;
			}
			break;
		case "select-one":
			aVals[oForm.elements[i].name]=oForm.elements[i]
				.options[oForm.elements[i].selectedIndex].value;
			break;
		default:
			aVals[oForm.elements[i].name]=oForm.elements[i].value;
			break;
		}
	}
	return aVals;
};


//namespace object for my http related functions
//The way I want to do "Ajax" 
//basically, I have the backend pass the data as a JS object, it's quicker than 
// generating then parsing XML... 
//apparently this caught on as JSON (JavaScript Ojbect Notation)
ajas.http={};

//An array to hold pending XMLHttpRequest objects
ajas.http.aRequests=[];       

//a simple object that contains the basic parts to fake a form.
ajas.http.oBlankForm={'method':'post','action':'',
	'enctype':'application/x-www-form-urlencoded','sData':''};

//add XMLHttpRequest object and parameters to queue
//keeping them in a queue simplifies the use of concurrent requests
//name of callback function can be passed, or taken from the querystring.
ajas.http.enqueue=function(oXML,aParams,sCallback){
	if(!sCallback)sCallback=aParams['callback']||'ajas.http.nullHandler';
	ajas.http.aRequests.push([oXML,
		new Function('oXML','aParams',sCallback+'(oXML,aParams);'),aParams]);
};



//This object is useful for 'fixing' the back/forward buttons
// in 'ajax' (navigation) based sites
ajas.http.HashHistory = function (fCallback,sIeEchoURL,iMillis) {
	var private={
		fCallback:fCallback,		//function to call on hash changes
		sCurrentHash:'',			//to maintain #hash of current state
		oPollTimer:null,			//timer to poll #hash state changes
		iPollTime:500,				//poll time interval in ms, default
		sEchoURL:sIeEchoURL,        //URL that echos the querystring like this:
	                                //<?='#'.$_SERVER['QUERY_STRING'];?>
		iKey:0
	};

	if (ajas._bIE) { //if IE, use an iframe to keep history
		private.oIframeDiv=document.createElement('div');
		private.oIframeDiv.style.display='none';
		private.oIframeDiv.innerHTML='<iframe style="display:none;" '
			+'src="about:blank" name="ajas_http_History'+private.iKey+'">'
			+'</iframe>';
		private.oIframeName='ajas_http_History'+private.iKey;
		document.body.appendChild(private.oIframeDiv);
	}
	var public={
		//called from links, set the hashes that are polled to match link clicked
		addHash:function(sHash) {
			window.clearTimeout(private.oPollTimer);	//clear timer to avoid trigger during this function
			private.sCurrentHash=(sHash[0]=='#'?'':'#')+sHash;
			if (ajas._bIE) { //if IE
				//have to clear contents to deal with network latency
				try{window(private.oIframeName).document.body.innerText='';}catch(ex){}
				window(private.oIframeName).location=private.sEchoURL+'?'+private.sCurrentHash.substr(1);
			}
			window.location.hash=private.sCurrentHash;
			private.oPollTimer=window.setTimeout('ajas.util.store.get('+private.iKey+').pollHash();', private.iPollTime);
		},
		pollHash:function() {
			//Internet explorer needs special treatment.
			//don't forget to setup your echo URL described above
			if (ajas._bIE) {
				try{
					if (window.frames(private.oIframeName).document.body.innerText != private.sCurrentHash
						&& window.frames(private.oIframeName).document.body.innerText != '') {
						private.sCurrentHash = window.frames(private.oIframeName).document.body.innerText;
						window.location.hash = private.sCurrentHash;
						private.fCallback(private.sCurrentHash);
					}
				} catch(e){	}
			}
			//this portion handles FF, and IE's initial check
			if (window.location.hash != private.sCurrentHash
				&& window.location.hash != '') {
				private.sCurrentHash = window.location.hash;
				if (ajas._bIE) {
					try{window[private.oIframeName].document.body.innerText='';}catch(e){}
					window(private.oIframeName).location=private.sEchoURL+'?'+private.sCurrentHash.substr(1);
				}
				try{
					private.fCallback(private.sCurrentHash);
				}catch(e){}
			}
			private.oPollTimer=window.setTimeout('ajas.util.store.get('+private.iKey+').pollHash();', private.iPollTime);
			return true;
		}
	};
	private.iKey=ajas.util.store.add(public);
	public.pollHash();
	return public;
};


//get XMLHttpRequest object according to browser type
ajas.http.newRequest=function() {
	return window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP")
		:new XMLHttpRequest();
};

//this is the default, do nothing, response handler
//if you enqueue and aParams doesn't contain a 'callback' property it uses this
ajas.http.nullHandler=function(oXML, aParams) {
	//if (oXML.responseText=='')return;
	//eval('var aData='+oXML.responseText);
	// do something with the data
	//alert('ajas.http.nullHandler called');
};

//check each XMLRequest and handle the ready ones with their preset callback
ajas.http.queueHandler=function(){
	for(var key in ajas.http.aRequests){
		if (ajas.http.aRequests[key][0].readyState == 4){
			var oXML=ajas.http.aRequests[key][0];
			var oFunc=ajas.http.aRequests[key][1];
			var aParams=ajas.http.aRequests[key][2];
			delete ajas.http.aRequests[key];
			oFunc(oXML,aParams);
		}
	}
	return true;
};

//send a typical get request
//name of callback function can be passed, or taken from the querystring.
ajas.http.sendRequest=function(sUrl,sQueryString,sCallback){
	//if(arguments.length < 2) {
	if(!sQueryString) {
		var a=sUrl.split('?');
		sUrl=a[0];
		sQueryString=a[1];
	}
	var oXML=ajas.http.newRequest();
	oXML.open('GET',sUrl+'?'+sQueryString,true);//true=aSync
	oXML.onreadystatechange=ajas.http.queueHandler;
	oXML.setRequestHeader("Content-Type",'application/x-www-form-urlencoded');
	oXML.setRequestHeader("Connection", "close");
	oXML.send(null);

	var aParams=ajas.util.parseQueryString(sQueryString);
	ajas.http.enqueue(oXML,aParams,sCallback);
	return false; // so the form submit cancels
};

//take a form's data and send it in an XMLHttpRequest
ajas.http.submitForm=function(oForm,sCallback){
	var oXML=ajas.http.newRequest();
	var formData=oForm.sData||
		ajas.util.buildQueryString(ajas.form.getValues(oForm));
	oXML.open(oForm.method,oForm.action,true);//true=aSync
	oXML.onreadystatechange=ajas.http.queueHandler;
	oXML.setRequestHeader("Content-Type",oForm.enctype);
	oXML.setRequestHeader("Connection", "close");
	oXML.setRequestHeader("Content-length", formData.length);
	oXML.send(formData);

	var aParams;
	if(oForm.action.indexOf('?')==-1){
		aParams=[];
	} else {
		aParams=ajas.util.parseQueryString(
			oForm.action.substr(oForm.action.indexOf('?')+1));
	}

	//name of callback function can be passed, or taken from the querystring.
	ajas.http.enqueue(oXML,aParams,sCallback);
	return false; // so the form submit cancels
};

//builds and sends an http request with data encoded like an attached file.
ajas.http.uploadAsFile=function(sUrl,sFilename,sData,sCallback,iMaxLen) {
	if(arguments.length<5)var iMaxLen=false;
	if(arguments.length<4)var sCallback='ajas.http.nullHandler';
	if(arguments.length<3)return false;
	// prepare the MIME POST data
	var boundaryString='\\\\boundary\\\\';
	var boundary='--'+boundaryString;
	var requestbody=boundary+'\n'
	+'Content-Disposition: form-data; name="file"; filename="'+sFilename+'"\n'
	+'Content-Type: application/octet-stream\n\n'
	+ajas.util.escapePlus(sData)
	+'\n'+boundary;

	oForm={'method':'POST','action':sUrl+'?callback='+sCallback,
		'sData':requestbody,
		'enctype':'multipart/form-data; boundary="'+boundaryString+'"'};
	ajas.http.submitForm(oForm);
};

//Reads a local file into buffer and calls ajas.http.uploadAsFile()
ajas.http.uploadLocalFile=function(sUrl,sFilename,sCallback,iMaxLen) {
	if(arguments.length < 4)var iMaxLen=false;
	if(arguments.length < 3)var sCallback='ajas.http.nullHandler';
	// FireFox/Mozilla settings: Open about:config and check that
	//  [signed.applets.codebase_principal_support] is set to "true"
	// request local file read permission
	netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");

	// open the local file
	var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
	file.initWithPath( sFilename );
	stream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
	stream.init(file, 0x01, 00004, null);
	var bstream =  Components.classes["@mozilla.org/network/buffered-input-stream;1"].getService();
	bstream.QueryInterface(Components.interfaces.nsIBufferedInputStream);
	bstream.init(stream, 1000);
	bstream.QueryInterface(Components.interfaces.nsIInputStream);
	var binary = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream);
	binary.setInputStream (stream);

	if (iMaxLen && binary.available() > iMaxLen) {
		throw new Error('File size exceeds expressed limit of '+iMaxLen+' Bytes.');
		return false;
	}
	sFilename=sFilename.substr(Math.max(sFilename.lastIndexOf('/'),sFilename.lastIndexOf('\\'))+1);
	ajas.http.uploadAsFile(sUrl,sFilename,binary.readBytes(binary.available()),sCallback,iMaxLen);
	binary.close();
	bstream.close();
	stream.close();
};


ajas.util={};


//set the clipboard
//still trying to get this to work without the swf
ajas.util.copy=function(s){
	try{
		clipboardData.setData("Text", s);
	}catch(e){
		//credit for FF solution to http://www.jeffothy.com/weblog/clipboard-copy/
		var f='flashcopier';
		if(!ajas._e(f)) {
			var o=document.createElement('div');
			o.id=f;
	//		o.style.display='none';
			document.body.appendChild(o);
//			o.innerHTML = '<input type="text"><iframe src="data:text/html;charset=utf-8;base64,'
//				+btoa('<embed src="data:application/x-shockwave-flash;base64,Q1dTB3kAAAB4nKtgYI1nYOBfwMDAw8jgzPT%2F%2F3975lAGBoYOdQYWhuSczIKk%2FMSiFIac1Lz0kgyG4MriktRchuLUEme41DQmBg4GGRDJ6Cc0l4lBAibCzsDOCDSJgwksyRwkzuAA5AIAd7oY%2Fw%3D%3D" FlashVars="code='+encodeURIComponent(s)+'"></embed>')+'"></iframe>';
		}
//		ajas._e(f).innerHTML = '<embed src="data:application/x-shockwave-flash;base64,Q1dTB3kAAAB4nKtgYI1nYOBfwMDAw8jgzPT//3975lAGBoYOdQYWhuSczIKk/MSiFIac1Lz0kgyG4MriktRchuLUEme41DQmBg4GGRDJ6Cc0l4lBAibCzsDOCDSJgwksyRwkzuAA5AIAd7oY/w==" FlashVars="clipboard='+encodeURIComponent(s)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>';
		ajas._e(f).innerHTML = '<embed src="http://www.woktiny.net/wheel/_clipboard.swf" FlashVars="clipboard='+encodeURIComponent(s)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>';
//		ajas._e(f).innerHTML = '<embed src="Q1dTB3kAAAB4nKtgYI1nYOBfwMDAw8jgzPT%2F%2F3975lAGBoYOdQYWhuSczIKk%2FMSiFIac1Lz0kgyG4MriktRchuLUEme41DQmBg4GGRDJ6Cc0l4lBAibCzsDOCDSJgwksyRwkzuAA5AIAd7oY%2Fw%3D%3D" FlashVars="clipboard='+encodeURIComponent(s)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>';
//		ajas._e(f).contentWindow.document.body.innerHTML = '<input type="text"><embed src="data:application/x-shockwave-flash;base64,Q1dTB3kAAAB4nKtgYI1nYOBfwMDAw8jgzPT%2F%2F3975lAGBoYOdQYWhuSczIKk%2FMSiFIac1Lz0kgyG4MriktRchuLUEme41DQmBg4GGRDJ6Cc0l4lBAibCzsDOCDSJgwksyRwkzuAA5AIAd7oY%2Fw%3D%3D" FlashVars="code='+encodeURIComponent(s)+'"></embed>';
//Q1dTB3kAAAB4nKtgYI1nYOBfwMDAw8jgzPT%2F%2F3975lAGBoYOdQYWhuSczIKk%2FMSiFIac1Lz0kgyG4MriktRchuLUEme41DQmBg4GGRDJ6Cc0l4lBAibCzsDOCDSJgwksyRwkzuAA5AIAd7oY%2Fw%3D%3D
//TinyURL.com/2yh44h
	}
};	


ajas.string={};

ajas.string.aLpha={'A':'Alpha','B':'Bravo','C':'Charlie','D':'Delta',
	'E':'Echo','F':'Foxtrot','G':'Golf','H':'Hotel','I':'India','J':'Juliet',
	'K':'Kilo','L':'Lima','M':'Mike','N':'November','O':'Oscar','P':'Papa',
	'Q':'Quebec','R':'Romeo','S':'Sierra','T':'Tango','U':'Uniform',
	'V':'Victor','W':'Whiskey','X':'X-ray','Y':'Yankee','Z':'Zulu'};

ajas.string.sB64=
	'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
ajas.string.sB32='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
ajas.string.sHex='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';


//create a string representation of int i in radix r
ajas.string.unparseNumber=function(i,r){
	var s='';
	while (i>0){
		s=ajas.string.sHex.charAt(i%r)+s;
		i=Math.floor(i/r);
	}
	return s;
};
//parse string s to an int of radix r
ajas.string.parseNumber=function(s,r) {
	var a=0,m=1,t=0;
	for (var i=s.length-1;i>=0;i--) {
		t=ajas.string.sHex.indexOf(s.charAt(i));
		if(t>=r) return false;
		a+=m*t;
		m*=r;
	}
	return a;
};
//convert a hex string to an ascii string
ajas.string.hex2ascii=function(sH) {
	var a=sH.split(' ');
	var sA='';
	for (i in a) {
		sA+=String.fromCharCode(ajas.string.parseNumber(a[i],16));
	}
	return sA;
};
//convert an ascii string to to a hex string
ajas.string.ascii2hex=function(sA) {
	var a=sA.split('');
	for (i in a) {
		a[i]=ajas.string.padLeft(ajas.string.unparseNumber(
			a[i].charCodeAt(0),16),2,'0');
	}
	return a.join(' ');
};
//convert a binary string to an ascii string
ajas.string.binary2ascii=function(sB) {
	var a=sB.split(' ');
	var sA='';
	for (i in a) {
		sA+=String.fromCharCode(ajas.string.parseNumber(a[i],2));
	}
	return sA;
};
//convert an ascii string to to a binary string
ajas.string.ascii2binary=function(sA) {
	var a=sA.split('');
	for (i in a) {
		a[i]=ajas.string.padLeft(ajas.string.unparseNumber(
			a[i].charCodeAt(0),2),8,'0');
	}
	return a.join(' ');
};

//pad a string to length
ajas.string.padLeft=function(s,l,p){
	if(arguments.length<3)p=' ';
	var q='',m=l-s.length;
	while(q.length<m)q+=p;
	return m>0?q.substr(0,m)+s:s;
};
//pad a string to length
ajas.string.padRight=function(s,l,p){
	if(arguments.length<3)p=' ';
	var q='',m=l-s.length;
	while(q.length<m)q+=p;
	return m>0?s+q.substr(0,m):s;
};

ajas.string.oROT13map={
	'a':'n','b':'o','c':'p','d':'q','e':'r','f':'s','g':'t','h':'u','i':'v','j':'w','k':'x','l':'y','m':'z',
	'n':'a','o':'b','p':'c','q':'d','r':'e','s':'f','t':'g','u':'h','v':'i','w':'j','x':'k','y':'l','z':'m',
	'A':'N','B':'O','C':'P','D':'Q','E':'R','F':'S','G':'T','H':'U','I':'V','J':'W','K':'X','L':'Y','M':'Z',
	'N':'A','O':'B','P':'C','Q':'D','R':'E','S':'F','T':'G','U':'H','V':'I','W':'J','X':'K','Y':'L','Z':'M'
	};
//16.92 time score
//ROT13 aided by array based map; fastest, if you don't mind the map in memory
ajas.string.rot13=function(s){
	var c='';
	for(var i=0; i<s.length;i++){
		c+=ajas.string.aROT13map[s.charAt(i)]||s.charAt(i);
	}
	return c;
};

//24.0 time score
//ROT13 aided by string based map; not bad, not great
ajas.string.rot13_sm=function(s){
	var a='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
	var b='nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM';
	var c='';
	for(var i=0;i<s.length;i++){
		c+=a.charAt(b.indexOf(s.charAt(i)))||s.charAt(i);
	}
	return c;
};

//21.23 time score
//ROT13 by traditional char value checking
ajas.string.rot13_if=function(s){
	var c='';
	var b;
	for(var i=0; i<s.length;i++){
		b=s.charCodeAt(i);
		if(((b>64)&&(b<78))||((b>96)&&(b<110))){
			b+=13;
		} else if (((b>77)&&(b<91))||((b>109)&&(b<123))) {
			b-=13;
		}
		c+=String.fromCharCode(b);
	}
	return c;
};


//40.86 time score
//ROT13 aided by regular expression replace; beautiful, but slow
ajas.string.rot13_re=function(s) {
	return s.replace(/[a-zA-Z]/g, function(c){
		return String.fromCharCode((c <= 'Z' ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
	});
};

//Return an array of counts for each letter in string, useful for pangram checking
ajas.string.alphaCount=function(ref) {
	var aCounts={'a':0,'b':0,'c':0,'d':0,'e':0,'f':0,'g':0,'h':0,'i':0,'j':0,'k':0,'l':0,'m':0,'n':0,'o':0,'p':0,'q':0,'r':0,'s':0,'t':0,'u':0,'v':0,'w':0,'x':0,'y':0,'z':0};
	var s=ref.value;
	for (var i=0;i<s.length;i++) {
		var j=s.charAt(i).toLowerCase();
	}
	return aCounts;
};

ajas.util={};

//takes a basic ?key=val&key2=val2 string and returns an associative array
ajas.util.parseQueryString=function(sQueryString) {
	sQueryString = sQueryString.replace(/^\?/, '');//toss leading ?
	if (sQueryString.length == 0) return;
	aParams=new Array();
	sQueryString = sQueryString.replace(/\+/g, ' ');//interpret '+' as space
	var aPairs = sQueryString.split('&');
	for(var i in aPairs) {
		var pair = aPairs[i].split('=',2);
		aParams[unescape(pair[0])]=pair.length==2?unescape(pair[1]):'';
	}
	return aParams;
};

//JS escape that also handles + -> %2b
ajas.util.escapePlus=function(s){
	return escape(s).replace(/\053/g,'%2b');
};


//takes an associative array and returns  a basic ?key=val&key2=val2 string
ajas.util.buildQueryString=function(aParams) {
	var a=[];
	for(var i in aParams) {
		a.push(ajas.util.escapePlus(i)+'='+ajas.util.escapePlus(aParams[i]));
	}
	return a.join('&');
};

//takes an array and gens some printable text, better put output in <pre>
ajas.util.arrayDump=function(aRef, sPre) {
	if(arguments.length<2) sPre='\n';
	var sTEXT = 'Array {';
	var sPreN = sPre+'  ';
	for(var i in aRef){
		if (typeof(aRef[i]) == 'object' && typeof(aRef[i]) != 'string') {
			sTEXT += ''+sPreN+i+" => "+ajas.util.arrayDump(aRef[i],sPreN);
		} else {
			sTEXT += ''+sPreN+i+" => "+aRef[i];
		}  
	}
	return sTEXT+''+sPre+'}';
};

//takes an array and gens a PHP definition
ajas.util.arrayToPhp=function(aRef) {
	var a=[];
	for(var i in aRef){
		if (typeof(aRef[i]) == 'object' && typeof(aRef[i]) != 'string') {
			a.push("'"+i+"' => "+ajas.util.arrayToPhp(aRef[i]));
		} else {
			a.push("'"+i+"' => '"+aRef[i]+"'");
		}  
	}
	return 'array('+a.join(',')+')';
};
//takes an array and gens some crude tables
ajas.util.arrayToTable=function(aRef, sPre) {
	if(arguments.length == 1) sPre='';
	var sTEXT = '<table border=1 style=\"border:1px solid black;border-collapse:collapse;\">';
	for(var i in aRef){
		if (typeof(aRef[i]) == 'object' && typeof(aRef[i]) != 'string') {
			sTEXT += '<tr><td>'+i+'</td><td>'+ajas.util.arrayToTable(aRef[i],sPreN)+'</td></tr>';
		} else {
			sTEXT += '<tr><td>'+i+'</td><td>'+aRef[i]+'</td></tr>';
		}  
	}
	return sTEXT+'</table>';
};

// simple object for storing immutable (self)references
ajas.util.store=function(){
	var private={
		list:{},
		i:0
	};
	var public={
		add:function(o){
			private.list[private.i]=o;
			return private.i++;
		},
		get:function(i){
			return private.list[i];
		}
	};
	return public;
}();


/******************************************************************************
 * http://ajas.us/
 * The way I want to do CreditCards (Apr 27, 2007) rev. (May 14, 2007)
 * If you find an error, or improvement, let me know: dev at ajas dot us
 * This file is free for use
 *
 * ajas.ui.magicCreditCard()           - call this onblur, scrubs and validates
 * ajas.validator.creditCard.validate()- call for true/false validation
 * ajas.validator.creditCard.parse()   - don't call this
 * ajas.validator.creditCard.infer()   - just because I could, has ambiguity 
 * ajas.validator.creditCard.aCards    - details for each card type to check 
 * 
 * based on: http://www.braemoor.co.uk/software/creditcard.shtml
 *****************************************************************************/

if(!ajas.ui)ajas.ui={};
if(!ajas.validator)ajas.validator={};
if(!ajas.validator.creditCard)ajas.validator.creditCard={};


// use this function for form feedback
ajas.ui.magicCreditCard=function(oInput, sType, bStrict) {
	// Set preferred defaults here.
	// bStrict=false will remove invalid characters
	// bStrict=true will fail on invalid characters
	if(arguments.length < 3) var bStrict=true;

	var sLabel = oInput.id + 'Msg';
	oInput.className = oInput.className.replace(/ajas_credit_error/g, '');
	try {
		if (oInput.value == '') {
			ajas._e(sLabel).innerHTML = 'Enter Credit Card';
			return;
		}
		var sCC = ajas.validator.creditCard.parse(oInput.value, sType, bStrict);
		ajas._e(sLabel).innerHTML = oInput.value = sCC;
	} catch (e) {
		oInput.className += ' ajas_credit_error';
		var message = e.message;
		// Fix for IE6 bug
		if (message.indexOf('is null or not an object') > -1) {
			message = 'Invalid Phone string';
		}
		ajas._e(sLabel).innerHTML = message;
	}
};

//use this function to get a quiet true/false response
ajas.validator.creditCard.validate=function(sCC, sType) {
	try {
		ajas.validator.creditCard.parse(sCC, sType, true); //bStrict=true
	} catch (e) {
		return false;
	}
	return true;
};

ajas.validator.creditCard.parse=function(sCC, sType, bStrict) {
	if(arguments.length < 3) var bStrict=true;

	if (false==bStrict) {
		//strip non-digits
		sCC=sCC.replace(/[^\d]/g,'');
	} else {
		//strip only spaces and dashes
		sCC=sCC.replace(/[\s\-]/g,'');
		
		// verify number is actually numeric
		if (!/^\d*$/.exec(sCC)) {
			throw new Error("Invalid Credit Card #");
		}
	}
	
	if (sCC.length == 0) {
		throw new Error("Enter Credit Card #");
	}

	//verify specified type is known valide
	if (!ajas.validator.creditCard.aCards[sType]) {
		throw new Error("Unsupported Card Type");
	}
	
	//verify specified number matches card type prefix
	if (!new RegExp('^'+ajas.validator.creditCard.aCards[sType].prefixes).test(sCC)) {
		throw new Error("Invalid "+sType+" #");
	}
	
	//verify specified number is of valid length for type
	if (!new RegExp('^'+ajas.validator.creditCard.aCards[sType].len).test(sCC.length)) {
		throw new Error("Invalid Number Length");
	}

	//verify check-digit, if card type specifies
	if (ajas.validator.creditCard.aCards[sType].bChkDig) {
		var iSum=0;
		for(var i=sCC.length-1,j=true;i>=0;i--,j=!j) {
			l=0;
			if (j) {
				iSum+=parseInt(sCC.charAt(i));
			} else { // add the sum of the digits
				if ((k=parseInt(sCC.charAt(i))) > 4) {
					iSum+=2*k-9;
				} else {
					iSum+=2*k;
				}
			}
		}
		if (iSum%10!=0) {
			throw new Error("Invalid CC (checksum)"+iSum);
		}
	}
	return sCC;
};

ajas.validator.creditCard.infer=function(sCC) {
	//strip non-digits
	sCC=sCC.replace(/[^\d]/g,'');

	var aCandidates = new Array();
	var i=0;
	
	//Find which cards' prefixes match supplied card
	for(var s in ajas.validator.creditCard.aCards) {
		if (new RegExp('^'+ajas.validator.creditCard.aCards[s].prefixes).test(sCC)) {
			aCandidates[i++]=s;
		}
	}
	
	if (aCandidates.length==0) return '[No Prefix Match]';
	var aCandidates2 = new Array();
	i=0;
	
	//Find which cards' lengths match remaining candidates
	for(var j in aCandidates) {
		if (new RegExp('^'+ajas.validator.creditCard.aCards[aCandidates[j]].len).test(sCC.length)) {
			aCandidates2[i++]=aCandidates[j];
		}
	}

	if (aCandidates2.length==0) return '[No Length Match]';
	var aCandidates = new Array();
	var bChk = false;
	
	//verify check-digit
	var iSum=0;
	for(var i=sCC.length-1,j=true;i>=0;i--,j=!j) {
		l=0;
		if (j) {
			iSum+=parseInt(sCC.charAt(i));
		} else { // add the sum of the digits
			if ((k=parseInt(sCC.charAt(i))) > 4) {
				iSum+=2*k-9;
			} else {
				iSum+=2*k;
			}
		}
	}
	if (iSum%10==0) {
		bChk=true;
	}
	
	i=0;
	
	// if the checkdigit fails, remove candidates that require it
	if (!bChk) {
		for(var j in aCandidates2) {
			if (!ajas.validator.creditCard.aCards[j].bChkDig) {
				aCandidates[i++]=aCandidates2[j];
			}
		}
	} else {
		aCandidates=aCandidates2;
	}

	if (aCandidates.length==0) return '[No Parity Match]';

	return aCandidates.join(',');
};

// define known valid card types
ajas.validator.creditCard.aCards={
	"Visa"        :{bChkDig:true,len:"13|16"   ,prefixes:"4"},
	"MasterCard"  :{bChkDig:true,len:"16"      ,prefixes:"51|52|53|54|55"},
	"DinersClub"  :{bChkDig:true,len:"14|16"   ,prefixes:"300|301|302|303|304|305|36|38|55"},
	"CarteBlanche":{bChkDig:true,len:"14"      ,prefixes:"300|301|302|303|304|305|36|38"},
	"AmEx"        :{bChkDig:true,len:"15"      ,prefixes:"34|37"},
	"Discover"    :{bChkDig:true,len:"16"      ,prefixes:"6011|650"},
	"JCB"         :{bChkDig:true,len:"15|16"   ,prefixes:"3|1800|2131"},
	"enRoute"     :{bChkDig:true,len:"15"      ,prefixes:"2014|2149"},
	"Solo"        :{bChkDig:true,len:"16|18|19",prefixes:"6334|6767"},
	"Switch"      :{bChkDig:true,len:"16|18|19",prefixes:"4903|4905|4911|4936|564182|633110|6333|6759"},
	"Maestro"     :{bChkDig:true,len:"16"      ,prefixes:"5020|6"},
	"VisaElectron":{bChkDig:true,len:"16"      ,prefixes:"417500|4917|4913"}
};
/******************************************************************************
 * http://ajas.us/
 * The way I want to do dates (Dec 7, 2006) rev. (May 14, 2007)
 * If you find an error, or improvement, let me know: dev at ajas dot us
 * This file is free for use
 *
 * I have not tested every date, but it seems to work for all the dates tested
 *
 * ajas.ui.magicDate()               - call this onblur, scrubs and validates
 * ajas.util.arraySearch()           - return first index of item in array
 * ajas.util.arrayFilter()           - filters array based on passed test function
 * ajas.validator.date.parseMonth()  - returns which single monthname a string represents
 * ajas.validator.date.parseWeekday()- returns which single dayname a string represents
 * ajas.validator.date.parse()       - don't call this
 * ajas.ui.magicDate_keyup()         - call this on keyup, handles special keys
 * ajas.validator.date.aMonthNames
 * ajas.validator.date.aWeekdayNames
 *
 * The function cancelBubble() is found in ajas.event.js
 *
 * based on:
 *  'Magic' date parsing, by Simon Willison (6th October 2003)
 *  http://simon.incutio.com/archive/2003/10/06/betterDateInput
 *****************************************************************************/

if(!ajas.ui)ajas.ui={};
if(!ajas.util)ajas.util={};
if(!ajas.validator)ajas.validator={};
if(!ajas.validator.date)ajas.validator.date={};

ajas.validator.date.aMonthNames = ["January","February","March","April","May","June","July","August","September","October","November","December"];
ajas.validator.date.aWeekdayNames = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];


// use this function for form feedback
ajas.ui.magicDate=function(oInput, bStrict, bAmerican) {
	// Set preferred defaults here.
	// bStrict=false will convert Jan 32 to Feb 1
	if(arguments.length < 2) var bStrict=true;
	// bAmerican=false will expect European date forms, where applicable
	if(arguments.length < 3) var bAmerican=true;
	
	var sLabel = oInput.id + 'Msg';
	oInput.className = oInput.className.replace(/ajas_date_error/g, '');
	try {
		if (oInput.value == '') {
			ajas._e(sLabel).innerHTML = bAmerican?'mm/dd/yyyy':'dd/mm/yyyy';
			return;
		}
		var oDate = ajas.validator.date.parse(oInput.value,bStrict,bAmerican);
		oInput.value = ajas.util.dateFormat(oDate,bAmerican?ajas.util.dateFormat.USA:ajas.util.dateFormat.EUR);
		// Human readable date
		ajas._e(sLabel).innerHTML = oDate.toDateString();
	} catch (e) {
		oInput.className += ' ajas_date_error';
		var message = e.message;
		// Fix for IE6 bug
		if (message.indexOf('is null or not an object') > -1) {
			message = 'Invalid date string';
		}
		ajas._e(sLabel).innerHTML = message;
	}
};

ajas.util.dateFormat=function(oDate,iForm){
	if(arguments.length < 2) var iForm = 0;
	if(arguments.length < 1) var oDate = new Date();
	
	switch(iForm){
		case ajas.util.dateFormat.ISO: //ISO
			return oDate.getFullYear() + '-' + (oDate.getMonth() + 1) + '-' + oDate.getDate();
		case ajas.util.dateFormat.USA: //USA
			return (oDate.getMonth() + 1) + '/' + oDate.getDate() + '/' + oDate.getFullYear();
		case ajas.util.dateFormat.EUR: //EUR
			return oDate.getDate() + '/' + (oDate.getMonth() + 1) + '/' + oDate.getFullYear();
		case ajas.util.dateFormat.STR: //STR
			return oDate.toString();
		default:
			return 'Invalid Format {0:ISO,1:USA,2:EUR,4:STR}';
	}
};
ajas.util.dateFormat.ISO=0;
ajas.util.dateFormat.USA=1;
ajas.util.dateFormat.EUR=2;
ajas.util.dateFormat.STR=4;


/* Finds THE INDEX of the first occurence of item in the array, or false if not found */
ajas.util.arraySearch=function(aHaystack, needle) {
	for(var key in aHaystack) {
		if (aHaystack[key] == needle) {
			return key;
		}
	}
	return false;
};

/* Returns an array of items judged 'true' by the passed in test function */
ajas.util.arrayFilter=function(aHaystack, fTest) {
	var aMatches = [];
	for(var key in aHaystack) {
		if (fTest(aHaystack[key])) {
			aMatches[aMatches.length] = aHaystack[key];
		}
	}
	return aMatches;
};

/* Takes a string, returns the index of the month matching that string, throws
   an error if 0 or more than 1 matches
*/
ajas.validator.date.parseMonth=function(sMonth) {
	var aMatches = ajas.util.arrayFilter(ajas.validator.date.aMonthNames, function(item) { 
		return new RegExp("^" + sMonth, "i").test(item);
	});
	if (aMatches.length == 0) {
		throw new Error("Invalid month string");
	}
	if (aMatches.length > 1) {
		throw new Error("Ambiguous month");
	}
	return ajas.util.arraySearch(ajas.validator.date.aMonthNames, aMatches[0]);
};
/* Same as parseMonth but for days of the week */
ajas.validator.date.parseWeekday=function(sWeekday) {
	var aMatches = ajas.util.arrayFilter(ajas.validator.date.aWeekdayNames, function(item) {
		return new RegExp("^" + sWeekday, "i").test(item);
	});
	if (aMatches.length == 0) {
		throw new Error("Invalid day string");
	}
	if (aMatches.length > 1) {
		throw new Error("Ambiguous weekday");
	}
	return ajas.util.arraySearch(ajas.validator.date.aWeekdayNames, aMatches[0]);
};

ajas.validator.date.parse=function(sDate, bStrict, bAmerican) {
	if(arguments.length < 2) var bStrict=true;
	if(arguments.length < 3) var bAmerican=true;
	var aBits;
	var oDate = new Date();
	
	// Now
	if (aBits = /^now/i.exec(sDate)) {
		return oDate;
	}
	
	// Today
	if (aBits = /^tod(ay?)?$/i.exec(sDate)) {
		return oDate;
	}
	
	// Tomorrow
	if (aBits = new RegExp('^'+sDate,'i').test('tomorrow')) {
		oDate.setDate(oDate.getDate() + 1); 
		return oDate;
	}
	
	// Yesterday
	if (aBits = new RegExp('^'+sDate,'i').test('yesterday')) {
		oDate.setDate(oDate.getDate() - 1); 
		return oDate;
	}
	
	// nth
	if (aBits = /^(\d{1,2})(st|nd|rd|th)?$/i.exec(sDate)) {
		iMonth=oDate.getMonth();
		oDate.setDate(parseInt(aBits[1], 10));
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[1]+") days.");
		}
		return oDate;
	}

	// nth Month
	if (aBits = /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i.exec(sDate)) {
		oDate.setDate(1);
		oDate.setMonth(iMonth=ajas.validator.date.parseMonth(aBits[2]));
		oDate.setDate(parseInt(aBits[1], 10));
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[1]+") days.");
		}
		return oDate;
	}

	// nth Month YYYY
	if (aBits = /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{2,4})$/i.exec(sDate)) {
		oDate.setDate(1);
		var iYear=parseInt(aBits[3], 10);
		iYear = iYear < 70 ? iYear + 2000 : (iYear < 100 ? iYear + 1900 : ( iYear < 1000 ? iYear + 1000 : iYear) );
		oDate.setFullYear(iYear);
		oDate.setMonth(iMonth=ajas.validator.date.parseMonth(aBits[2]));
		oDate.setDate(parseInt(aBits[1], 10));
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[1]+") days.");
		}
		return oDate;
	}

	// Month nth
	if (aBits = /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i.exec(sDate)) {
		oDate.setDate(1);
		oDate.setMonth(iMonth=ajas.validator.date.parseMonth(aBits[1]));
		oDate.setDate(parseInt(aBits[2], 10));
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[2]+") days.");
		}
		return oDate;
	}

	// Month nth YYYY
	if (aBits = /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{2,4})$/i.exec(sDate)) {
		oDate.setDate(1);
		var iYear=parseInt(aBits[3], 10);
		iYear = iYear < 70 ? iYear + 2000 : (iYear < 100 ? iYear + 1900 : ( iYear < 1000 ? iYear + 1000 : iYear) );
		oDate.setFullYear(iYear);
		oDate.setMonth(iMonth=ajas.validator.date.parseMonth(aBits[1]));
		oDate.setDate(parseInt(aBits[2], 10));
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[2]+") days.");
		}
		return oDate;
	}

	// Tuesday - this is suspect due to varying expectations about implied next/last day
	if (aBits = /^(\w+)$/i.exec(sDate)) {
		var day = oDate.getDay();
		var newDay = ajas.validator.date.parseWeekday(aBits[1]);
		var addDays = newDay - day;
		oDate.setDate(oDate.getDate() + addDays);
		return oDate;
	}

	// Tuesday week - Australian way of saying a week from this coming Tuesday
	if (aBits = /^(\w+) week$/i.exec(sDate)) {
		var day = oDate.getDay();
		var newDay = ajas.validator.date.parseWeekday(aBits[1]);
		var addDays = newDay - day;
		if (newDay <= day) { // for coming [specified]day
			addDays += 7;
		}
		addDays += 7; // for the week after
		oDate.setDate(oDate.getDate() + addDays);
		return oDate;
	}

	// next Tuesday - this is suspect due to sometimes ambiguous meaning of "next"
	if (aBits = /^next? (\w+)$/i.exec(sDate)) {
		var day = oDate.getDay();
		var newDay = ajas.validator.date.parseWeekday(aBits[1]);
		var addDays = newDay - day;
		if (newDay <= day) {
			addDays += 7;
		}
		oDate.setDate(oDate.getDate() + addDays);
		return oDate;
	}

	// last Tuesday - this is suspect due to sometimes ambiguous meaning of "last"
	if (aBits = /^last? (\w+)$/i.exec(sDate)) {
		var day = oDate.getDay();
		var newDay = ajas.validator.date.parseWeekday(aBits[1]);
		var addDays = newDay - day;
		if (newDay >= day) {
			addDays -= 7;
		}
		oDate.setDate(oDate.getDate() + addDays);
		return oDate;
	}

	// mm/dd(/(cc)yy) (American style)
	// dd/mm(/(cc)yy) (European style) depends on bAmerican flag set to false
	if (aBits = /^(\d{1,2})[\/-](\d{1,2})([\/-](\d{2,4}))?$/.exec(sDate)) {
//		alert(ajas.util.arrayDump(aBits));
		if (!bAmerican) {
			var tmp=aBits[1];
			aBits[1]=aBits[2];
			aBits[2]=tmp;
		}
		oDate.setDate(1);
		if (aBits[4]) {
			var iYear=parseInt(aBits[4], 10);
			iYear = iYear < 70 ? iYear + 2000 : (iYear < 100 ? iYear + 1900 : ( iYear < 1000 ? iYear + 1000 : iYear) );
			oDate.setFullYear(iYear);
		}
		oDate.setMonth(iMonth=parseInt(aBits[1], 10) - 1); // Because months indexed from 0
		oDate.setDate(parseInt(aBits[2], 10));
		if (bStrict && iMonth >= 12) {
			throw new Error("Invalid month ("+(iMonth+1)+").");
		}
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[2]+") days.");
		}
		return oDate;
	}

	// yyyy-mm-dd (ISO style)
	if (aBits = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(sDate)) {
		oDate.setDate(1);
		var iYear=parseInt(aBits[1], 10);
		oDate.setFullYear(iYear);
		oDate.setMonth(iMonth=parseInt(aBits[2], 10) - 1); // Because months indexed from 0
		oDate.setDate(parseInt(aBits[3], 10));
		if (bStrict && iMonth >= 12) {
			throw new Error("Invalid month ("+(iMonth+1)+").");
		}
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[3]+") days.");
		}
		return oDate;
	}

	// now that I've checked what and how I want to check, let the JS API try what's left.
	if (milli = Date.parse(sDate)) { // is there a better way to do this?
		oDate.setSeconds(0);
		oDate.setMinutes(0);
		oDate.setHours(0);
		oDate.setDate(1);
		oDate.setFullYear(1970);
		oDate.setMonth(0); // Because months indexed from 0
		oDate.setMilliseconds(milli);
//	throw new Error(oDate);
		return oDate;
	}
	
	
	// no date
	throw new Error("Enter a Valid Date.");
};

ajas.ui.magicDate_keyup=function(event, bStrict, bAmerican) {
	// Set preferred defaults here.
	// bStrict=false will convert Jan 32 to Feb 1
	if(arguments.length < 2) var bStrict=true;
	// bAmerican=false will expect European date forms, where applicable
	if(arguments.length < 3) var bAmerican=true;

	event=event||window.event;
	var oInput=event.target||event.srcElement;
	if (false && "" == oInput.value){
		oInput.value = ajas.util.dateFormat(new Date(),bAmerican?ajas.util.dateFormat.USA:ajas.util.dateFormat.EUR);
	} else {
		key = event.keyCode?event.keyCode:event.which;
		
		switch(key) {
		case 13: // enter
			oInput.blur();
			ajas.event.cancelBubble(event);
			return false;
			break;
		case 38: // up
			if (oInput.value == '') oInput.value='today';
			var oDate = ajas.validator.date.parse(oInput.value, bStrict, bAmerican);
			oDate.setDate(oDate.getDate() + 1);
			oInput.value = ajas.util.dateFormat(oDate,bAmerican?ajas.util.dateFormat.USA:ajas.util.dateFormat.EUR);

			break;
		case 40: // down
			if (oInput.value == '') oInput.value='today';
			var oDate = ajas.validator.date.parse(oInput.value, bStrict, bAmerican);
			oDate.setDate(oDate.getDate() - 1);
			oInput.value = ajas.util.dateFormat(oDate,bAmerican?ajas.util.dateFormat.USA:ajas.util.dateFormat.EUR);
			break;
		case 39: // right
			if (oInput.value != '') break;
			oInput.value='today'; 
			var oDate = ajas.validator.date.parse(oInput.value, bStrict, bAmerican);
			oInput.value = ajas.util.dateFormat(oDate,bAmerican?ajas.util.dateFormat.USA:ajas.util.dateFormat.EUR);
			break;
		}
	}
	return true;
};

/*
	// mm/dd (American style) omitted year
	// dd/mm (European style) omitted year depends on bAmerican flag set to false
	if (aBits = /(\d{1,2})\/(\d{1,2})/.exec(sDate)) {
		if (!bAmerican) {
			var tmp=aBits[1];
			aBits[1]=aBits[2];
			aBits[2]=tmp;
		}
		oDate.setDate(1);
		oDate.setMonth(iMonth=parseInt(aBits[1], 10) - 1); // Because months indexed from 0
		oDate.setDate(parseInt(aBits[2], 10));
		if (bStrict && iMonth >= 12) {
			throw new Error("Invalid month ("+(iMonth+1)+").");
		}
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[2]+") days.");
		}
		return oDate;
	}
	// mm-dd-(cc)yy (American style) with dashes
	// dd-mm-(cc)yy (European style) with dashes depends on bAmerican flag set to false
	if (aBits = /(\d{1,2})-(\d{1,2})-(\d{2,4})/.exec(sDate)) {
		if (!bAmerican) {
			var tmp=aBits[1];
			aBits[1]=aBits[2];
			aBits[2]=tmp;
		}
		oDate.setDate(1);
		var iYear=parseInt(aBits[3], 10);
		iYear = iYear < 70 ? iYear + 2000 : (iYear < 100 ? iYear + 1900 : ( iYear < 1000 ? iYear + 1000 : iYear) );
		oDate.setFullYear(iYear);
		oDate.setMonth(iMonth=parseInt(aBits[1], 10) - 1); // Because months indexed from 0
		oDate.setDate(parseInt(aBits[2], 10));
		if (bStrict && iMonth >= 12) {
			throw new Error("Invalid month ("+(iMonth+1)+").");
		}
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[2]+") days.");
		}
		return oDate;
	}

	// mm-dd (American style) with dashes, omitted year
	// dd-mm (European style) with dashes, omitted year depends on bAmerican flag set to false
	if (aBits = /(\d{1,2})-(\d{1,2})/.exec(sDate)) {
		if (!bAmerican) {
			var tmp=aBits[1];
			aBits[1]=aBits[2];
			aBits[2]=tmp;
		}
		oDate.setDate(1);
		oDate.setMonth(iMonth=parseInt(aBits[1], 10) - 1); // Because months indexed from 0
		oDate.setDate(parseInt(aBits[2], 10));
		if (bStrict && iMonth >= 12) {
			throw new Error("Invalid month ("+(iMonth+1)+").");
		}
		if (bStrict && iMonth != oDate.getMonth()) {
			throw new Error("Too many ("+aBits[2]+") days.");
		}
		return oDate;
	}

*/

/******************************************************************************
 * http://ajas.us/
 * The way I want to do emails (Apr 11, 2007) rev. (Apr 20, 2007)
 * If you find an error, or improvement, let me know: dev at ajas dot us
 * This file is free for use
 *
 * ajas.ui.magicEmail()       - call this onblur, scrubs and validates
 * ajas.validator.email.validate()    - call for true/false validation
 * ajas.validator.email.parse()       - don't call this
 *
 * The function cancelBubble() is found in ajas.event.js
 *
 * TLD list taken from http://data.iana.org/TLD/tlds-alpha-by-domain.txt
 * ref: RFC822,RFC1035,icann.org,iana.org
 *****************************************************************************/

if(!ajas.ui)ajas.ui={};
if(!ajas.validator)ajas.validator={};
if(!ajas.validator.email)ajas.validator.email={};

// use this function for form feedback
ajas.ui.magicEmail=function(oInput, bStrict, bStrict2) {
	// Set preferred defaults here.
	// bStrict=true will validate the domain as an internet domain
	if(arguments.length < 2) var bStrict=true;
	// bStrict2=true will validate the TLD against a strict list
	//ONLY SET IF YOU KEEP THAT LIST UP TO DATE!
	if(arguments.length < 3) var bStrict2=false;
	
	var sLabel = oInput.id + 'Msg';
	oInput.className = oInput.className.replace(/ajas_email_error/g, '');
	try {
		if (oInput.value == '') {
			ajas._e(sLabel).innerHTML = 'user@domain'+(bStrict?'.tld':'');
			return;
		}
		var sEmail = ajas.validator.email.parse(oInput.value, bStrict, bStrict2);
		// Human readable email
		ajas._e(sLabel).innerHTML = oInput.value = sEmail;
	} catch (e) {
		oInput.className += ' ajas_email_error';
		var message = e.message;
		// Fix for IE6 bug
		if (message.indexOf('is null or not an object') > -1) {
			message = 'Invalid Email string';
		}
		ajas._e(sLabel).innerHTML = message;
	}

};

//use this function to get a quiet true/false response
ajas.validator.email.validate=function(oInput, bStrict, bStrict2) {
	if(arguments.length < 2) var bStrict=true;
	if(arguments.length < 3) var bStrict2=false;
	try {
		ajas.validator.email.parse(oInput.value, bStrict, bStrict2);
	} catch (e) {
		return false;
	}
	return true;
};

ajas.validator.email.parse=function(sEmail, bStrict, bStrict2) {
	if(arguments.length < 2) var bStrict=true;
	if(arguments.length < 3) var bStrict2=false;

	var aParts=/^([^@]+)@([^@]+)$/.exec(sEmail);//same as//var aParts=sEmail.match(/^(.+)@(.+)$/);
	if (aParts==null) {
		throw new Error("Wrong number of '@'");
	}
	var sUser=aParts[1];
	var sDomain=aParts[2];
	
	//NOT:   CTRL     SP  (   )   <   >   @   ,   ;   :   \   "   .   [   ]  DEL EXT
	sAtom='[^\000-\037\040\050\051\074\076\100\054\073\072\134\042\056\133\\]\177\200-\377]+';
	rAtom=new RegExp('^'+sAtom+'$');
	sQuoted='"[\040\041\043-\176]+"';
	rQuoted=new RegExp('^'+sQuoted+'$');
	rWord=new RegExp('^('+sQuoted+'|'+sAtom+')$');
	rLocalPart=new RegExp('^('+sQuoted+'|'+sAtom+')(\\.('+sQuoted+'|'+sAtom+'))*$');

	//test user
	if (sUser.match(rLocalPart) == null) {
		throw new Error("Invalid User part");
	}
	
	sDomainIP='\\[(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\]';
	rDomainIP=new RegExp('^'+sDomainIP+'$');
	rDomainNa=new RegExp('^'+sAtom+'(\\.'+sAtom+')*$');
	rDomain=new RegExp('^('+sDomainIP+'|('+sAtom+'(\\.'+sAtom+')*))$');

	//test domain
	if (sDomain.match(rDomain) == null) {
		throw new Error("Invalid Domain part");
	}
	
	//if IP, validate IP
	if (aBits=sDomain.match(rDomainIP)) {
		if (aBits[1]>255) throw new Error("Invalid IP address");
		if (aBits[2]>255) throw new Error("Invalid IP address");
		if (aBits[3]>255) throw new Error("Invalid IP address");
		if (aBits[4]>255) throw new Error("Invalid IP address");
	}
	//if bStrict && Named Domain, validate tld & RFC1035
	if (bStrict && sDomain.match(rDomainNa) != null) {
		var aBits=sDomain.split('.');

		if (bStrict2) { //check complete list
			//TLD list taken from http://data.iana.org/TLD/tlds-alpha-by-domain.txt
			var rTLD=/^(AC|AD|AE|AERO|AF|AG|AI|AL|AM|AN|AO|AQ|AR|ARPA|AS|ASIA|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BIZ|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CAT|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|COM|COOP|CR|CU|CV|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EDU|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GOV|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|INFO|INT|IO|IQ|IR|IS|IT|JE|JM|JO|JOBS|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MIL|MK|ML|MM|MN|MO|MOBI|MP|MQ|MR|MS|MT|MU|MUSEUM|MV|MW|MX|MY|MZ|NA|NAME|NC|NE|NET|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|ORG|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PRO|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SY|SZ|TC|TD|TEL|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TRAVEL|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|XN--0ZWM56D|XN--11B5BS3A9AJ6G|XN--80AKHBYKNJ4F|XN--9T4B11YI5A|XN--DEBA0AD|XN--G6W251D|XN--HGBK6AJ7F53BBA|XN--HLCJ6AYA9ESC7A|XN--JXALPDLP|XN--KGBECHTV|XN--ZCKZAH|YE|YT|YU|ZA|ZM|ZW)$/i;
			if (aBits[aBits.length-1].match(rTLD) == null) {
				throw new Error("Invalid Domain TLD");
			}
		} else { //check short list, and assume a 2 letter TLD is a valid one
			//TLD list taken from http://www.icann.org/registries/listing.html
			var rTLD=/^(aero|arpa|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel)$/i;
			if (aBits[aBits.length-1].length!=2 &&           //for country codes
				aBits[aBits.length-1].match(rTLD) == null) { //or standard TLDs
				throw new Error("Invalid Domain TLD");
			}
			if (aBits[aBits.length-1].length<2) { // single character TLD?!
				throw new Error("Invalid Domain TLD");
			}
		}
		if (aBits.length>1 && aBits[aBits.length-2].length<2) { // single character SLD?!
			throw new Error("Invalid Domain");
		}
		for(var i in aBits) { 
			// even though RFC1035 says start with a letter, we don't always
			//check for hyphens below to be more specfic in our error
			if (aBits[i].match(/^[a-zA-Z0-9\-]{1,63}$/) == null) {
				throw new Error("Invalid Domain Name");
			}
			if (aBits[i].charAt(0)=='-') {
				throw new Error("Leading Hyphen Error");
			}
			if (aBits[i].charAt(aBits[i].length-1)=='-') {
				throw new Error("Trailing Hyphen Error");
			}
			if (aBits[i].length > 63) {
				throw new Error("Maximum Length Error");
			}
		}
	}
	
	return sEmail;
};
/******************************************************************************
 * http://ajas.us/
 * The way I want to do phones (Apr 11, 2007) rev. (Apr 20, 2007)
 * If you find an error, or improvement, let me know: dev at ajas dot us
 * This file is free for use
 *
 * ajas.ui.magicPhone()           - call this onblur, scrubs and validates
 * ajas.validator.phone.validate()- call for true/false validation
 * ajas.validator.phone.parse()   - don't call this
 * ajas.validator.phone.areaCodes - listing of area codes and related info
 *                     found at: http://www.bennetyee.org/ucsd-pages/area.html
 *
 * The function cancelBubble() is found in ajas.event.js
 *****************************************************************************/

if(!ajas.validator.phone)ajas.validator.phone={};

// use this function for form feedback
ajas.ui.magicPhone=function(oInput, bStrict, bWordy) {
	// Set preferred defaults here.
	// bStrict=true will do some advanced checking on the area code
	if(arguments.length < 2) var bStrict=true;
	// bWordy allows dialpad letters
	if(arguments.length < 3) var bWordy=true;

	var sLabel = oInput.id + 'Msg';
	oInput.className = oInput.className.replace(/ajas_phone_email/g, '');
	try {
		if (oInput.value == '') {
			ajas._e(sLabel).innerHTML = '###-###-####';
			return;
		}
		var sPhone = ajas.validator.phone.parse(oInput.value, bStrict, bWordy);
		// Human readable phone
		ajas._e(sLabel).innerHTML = oInput.value = sPhone;
	} catch (e) {
		oInput.className += ' ajas_phone_email';
		var message = e.message;
		// Fix for IE6 bug
		if (message.indexOf('is null or not an object') > -1) {
			message = 'Invalid Phone string';
		}
		ajas._e(sLabel).innerHTML = message;
	}

};
//use this function to get a quiet true/false response

ajas.validator.phone.validate=function(oInput) {
	try {
		ajas.validator.phone.parse(oInput.value, true, false);
	} catch (e) {
		return false;
	}
	return true;
};

ajas.validator.phone.parse=function(sPhone, bStrict, bWordy) {
	if(arguments.length < 2) var bStrict=true;
	if(arguments.length < 3) var bWordy=true;

	if (bWordy) { //replace letters with numbers
		sPhone = sPhone.replace(/[a-c]/ig, '2');
		sPhone = sPhone.replace(/[d-f]/ig, '3');
		sPhone = sPhone.replace(/[g-i]/ig, '4');
		sPhone = sPhone.replace(/[j-l]/ig, '5');
		sPhone = sPhone.replace(/[m-o]/ig, '6');
		sPhone = sPhone.replace(/[p-s]/ig, '7');
		sPhone = sPhone.replace(/[t-v]/ig, '8');
		sPhone = sPhone.replace(/[w-z]/ig, '9');
	}
	if (aBits = /[a-z]/i.exec(sPhone)) {
		throw new Error("Invalid Phone Number");
	}

	sPhone = sPhone.replace(/[^0-9]/ig, '');

	//we don't really want the leading 1 in eleven digit US numbers
	if (sPhone.length == 11 && sPhone.charAt(0) == 1) {
		sPhone=sPhone.substr(1,11);
	}

	//no one's number starts with 911!
	if (sPhone.substr(0,3) == '911') {
		throw new Error("Invalid Phone Number");
	}

	if (sPhone.length == 10) { // Full american phone number
		if (bStrict) {
			if (sPhone.charAt(0) < 2 ||       //0,1 reserved
				sPhone.charAt(1) == '9' ||    //N9X - expansion codes reserved
				sPhone.substr(1,2) == '11' || //service codes
				sPhone.substr(0,2) == '37' || //37X - reserved
				sPhone.substr(0,2) == '96') { //96X - reserved
				throw new Error("Invalid Area Code");
			}
			if (sPhone.charAt(3) < 2) {
				throw new Error("Invalid Exchange");
			}
		}

		return sPhone.substr(0,3)+'-'+sPhone.substr(3,3)+'-'+sPhone.substr(6,4);
	}
	if (!bStrict && sPhone.length == 7) { // Local american phone number
		return sPhone.substr(0,3)+'-'+sPhone.substr(3,4);
	}

	throw new Error("Invalid Phone Length");
};

//http://www.bennetyee.org/ucsd-pages/area.html
ajas.validator.phone.areaCodes={
	'52' :{'Region':'MX','dst':true,'time':'-6','desc':'Mexico: Mexico City area (country code + city code)'},
	'55' :{'Region':'MX','dst':true,'time':'-6','desc':'Mexico: Mexico City area (country code + city code)'},
	'201':{'Region':'NJ','dst':true,'time':'-5','desc':'N New Jersey: Jersey City, Hackensack (see split 973, overlay 551)'},
	'202':{'Region':'DC','dst':true,'time':'-5','desc':'Washington, D.C.'},
	'203':{'Region':'CT','dst':true,'time':'-5','desc':'Connecticut: Fairfield County and New Haven County; Bridgeport, New Haven (see 860)'},
	'204':{'Region':'MB','dst':true,'time':'-6','desc':'Canada: Manitoba'},
	'205':{'Region':'AL','dst':true,'time':'-6','desc':'Central Alabama (including Birmingham; excludes the southeastern corner of Alabama and the deep south; see splits 256 and 334)'},
	'206':{'Region':'WA','dst':true,'time':'-8','desc':'W Washington state: Seattle and Bainbridge Island (see splits 253, 360, 425; overlay 564)'},
	'207':{'Region':'ME','dst':true,'time':'-5','desc':'Maine'},
	'208':{'Region':'ID','dst':true,'time':'-7/-8','desc':'Idaho'},
	'209':{'Region':'CA','dst':true,'time':'-8','desc':'Cent. California: Stockton (see split 559)'},
	'210':{'Region':'TX','dst':true,'time':'-6','desc':'S Texas: San Antonio (see also splits 830, 956)'},
	'211':{'Region':null,'dst':null,'time':null,'desc':'Local community info / referral services'},
	'212':{'Region':'NY','dst':true,'time':'-5','desc':'New York City, New York (Manhattan; see 646, 718)'},
	'213':{'Region':'CA','dst':true,'time':'-8','desc':'S California: Los Angeles (see 310, 323, 626, 818)'},
	'214':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: Dallas Metro (overlays 469/972)'},
	'215':{'Region':'PA','dst':true,'time':'-5','desc':'SE Pennsylvania: Philadelphia (see overlays 267)'},
	'216':{'Region':'OH','dst':true,'time':'-5','desc':'Cleveland (see splits 330, 440)'},
	'217':{'Region':'IL','dst':true,'time':'-6','desc':'Cent. Illinois: Springfield'},
	'218':{'Region':'MN','dst':true,'time':'-6','desc':'N Minnesota: Duluth'},
	'219':{'Region':'IN','dst':true,'time':'-6/-5','desc':'NW Indiana: Gary (see split 574, 260)'},
	'224':{'Region':'IL','dst':true,'time':'-6','desc':'Northern NE Illinois: Evanston, Waukegan, Northbrook (overlay on 847, eff 1/5/02)'},
	'225':{'Region':'LA','dst':true,'time':'-6','desc':'Louisiana: Baton Rouge, New Roads, Donaldsonville, Albany, Gonzales, Greensburg, Plaquemine, Vacherie (split from 504)'},
	'226':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: SW Ontario: Windsor (overlaid on 519; eff 6/06)'},
	'228':{'Region':'MS','dst':true,'time':'-6','desc':'S Mississippi (coastal areas, Biloxi, Gulfport; split from 601)'},
	'229':{'Region':'GA','dst':true,'time':'-5','desc':'SW Georgia: Albany (split from 912; see also 478; perm 8/1/00)'},
	'231':{'Region':'MI','dst':true,'time':'-5','desc':'W Michigan: Northwestern portion of lower Peninsula; Traverse City, Muskegon, Cheboygan, Alanson'},
	'234':{'Region':'OH','dst':true,'time':'-5','desc':'NE Ohio: Canton, Akron (overlaid on 330; perm 10/30/00)'},
	'236':{'Region':'VA','dst':true,'time':'-5','desc':'Virginia (region unknown) / Unassigned?'},
	'239':{'Region':'FL','dst':true,'time':'-5','desc':'Florida (Lee, Collier, and Monroe Counties, excl the Keys; see 305; eff 3/11/02; mand 3/11/03)'},
	'240':{'Region':'MD','dst':true,'time':'-5','desc':'W Maryland: Silver Spring, Frederick, Gaithersburg (overlay, see 301)'},
	'242':{'Region':null,'dst':true,'time':'-5','desc':'Bahamas'},
	'246':{'Region':null,'dst':true,'time':'-4','desc':'Barbados'},
	'248':{'Region':'MI','dst':true,'time':'-5','desc':'Michigan: Oakland County, Pontiac (split from 810; see overlay 947)'},
	'250':{'Region':'BC','dst':true,'time':'-8/-7','desc':'Canada: British Columbia (see 604)'},
	'251':{'Region':'AL','dst':true,'time':'-6','desc':'S Alabama: Mobile and coastal areas, Jackson, Evergreen, Monroeville (split from 334, eff 6/18/01; see also 205, 256)'},
	'252':{'Region':'NC','dst':true,'time':'-5','desc':'E North Carolina (Rocky Mount; split from 919)'},
	'253':{'Region':'WA','dst':true,'time':'-8','desc':'Washington: South Tier - Tacoma, Federal Way (split from 206, see also 425; overlay 564)'},
	'254':{'Region':'TX','dst':true,'time':'-6','desc':'Central Texas (Waco, Stephenville; split, see 817, 940)'},
	'256':{'Region':'AL','dst':true,'time':'-6','desc':'E and N Alabama (Huntsville, Florence, Gadsden; split from 205; see also 334)'},
	'260':{'Region':'IN','dst':true,'time':'-5','desc':'NE Indiana: Fort Wayne (see 219)'},
	'262':{'Region':'WI','dst':true,'time':'-6','desc':'SE Wisconsin: counties of Kenosha, Ozaukee, Racine, Walworth, Washington, Waukesha (split from 414)'},
	'264':{'Region':null,'dst':true,'time':'-4','desc':'Anguilla (split from 809)'},
	'267':{'Region':'PA','dst':true,'time':'-5','desc':'SE Pennsylvania: Philadelphia (see 215)'},
	'268':{'Region':null,'dst':true,'time':'-4','desc':'Antigua and Barbuda (split from 809)'},
	'269':{'Region':'MI','dst':true,'time':'-5','desc':'SW Michigan: Kalamazoo, Saugatuck, Hastings, Battle Creek, Sturgis to Lake Michigan (split from 616)'},
	'270':{'Region':'KY','dst':true,'time':'-6','desc':'W Kentucky: Bowling Green, Paducah (split from 502)'},
	'276':{'Region':'VA','dst':true,'time':'-5','desc':'S and SW Virginia: Bristol, Stuart, Martinsville (split from 540; perm 9/1/01, mand 3/16/02)'},
	'278':{'Region':'MI','dst':true,'time':'-5','desc':'Michigan (overlaid on 734, SUSPENDED)'},
	'281':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: Houston Metro (split 713; overlay 832)'},
	'283':{'Region':'OH','dst':true,'time':'-5','desc':'SW Ohio: Cincinnati (cancelled: overlaid on 513)'},
	'284':{'Region':null,'dst':true,'time':'-4','desc':'British Virgin Islands (split from 809)'},
	'289':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: S Cent. Ontario: Greater Toronto Area -- Durham, Halton, Hamilton-Wentworth, Niagara, Peel, York, and southern Simcoe County (excluding Toronto -- overlaid on 905, eff 6/9/01)'},
	'301':{'Region':'MD','dst':true,'time':'-5','desc':'W Maryland: Silver Spring, Frederick, Camp Springs, Prince George\'s County (see 240)'},
	'302':{'Region':'DE','dst':true,'time':'-5','desc':'Delaware'},
	'303':{'Region':'CO','dst':true,'time':'-7','desc':'Central Colorado: Denver (see 970, also 720 overlay)'},
	'304':{'Region':'WV','dst':true,'time':'-5','desc':'West Virginia'},
	'305':{'Region':'FL','dst':true,'time':'-5','desc':'SE Florida: Miami, the Keys (see 786, 954; 239)'},
	'306':{'Region':'SK','dst':false,'time':'-6/-7','desc':'Canada: Saskatchewan'},
	'307':{'Region':'WY','dst':true,'time':'-7','desc':'Wyoming'},
	'308':{'Region':'NE','dst':true,'time':'-6/-7','desc':'W Nebraska: North Platte'},
	'309':{'Region':'IL','dst':true,'time':'-6','desc':'W Cent. Illinois: Peoria'},
	'310':{'Region':'CA','dst':true,'time':'-8','desc':'S California: Beverly Hills, West Hollywood, West Los Angeles (see split 562; overlay 424)'},
	'311':{'Region':null,'dst':null,'time':null,'desc':'Reserved for special applications'},
	'312':{'Region':'IL','dst':true,'time':'-6','desc':'Illinois: Chicago (downtown only -- in the loop; see 773; overlay 872)'},
	'313':{'Region':'MI','dst':true,'time':'-5','desc':'Michigan: Detroit and suburbs (see 734, overlay 679)'},
	'314':{'Region':'MO','dst':true,'time':'-6','desc':'SE Missouri: St Louis city and parts of the metro area only (see 573, 636, overlay 557)'},
	'315':{'Region':'NY','dst':true,'time':'-5','desc':'N Cent. New York: Syracuse'},
	'316':{'Region':'KS','dst':true,'time':'-6','desc':'S Kansas: Wichita (see split 620)'},
	'317':{'Region':'IN','dst':true,'time':'-5','desc':'Cent. Indiana: Indianapolis (see 765)'},
	'318':{'Region':'LA','dst':true,'time':'-6','desc':'N Louisiana: Shreveport, Ruston, Monroe, Alexandria (see split 337)'},
	'319':{'Region':'IA','dst':true,'time':'-6','desc':'E Iowa: Cedar Rapids (see split 563)'},
	'320':{'Region':'MN','dst':true,'time':'-6','desc':'Cent. Minnesota: Saint Cloud (rural Minn, excl St. Paul/Minneapolis)'},
	'321':{'Region':'FL','dst':true,'time':'-5','desc':'Florida: Brevard County, Cape Canaveral area; Metro Orlando (split from 407)'},
	'323':{'Region':'CA','dst':true,'time':'-8','desc':'S California: Los Angeles (outside downtown: Hollywood; split from 213)'},
	'325':{'Region':'TX','dst':true,'time':'-6','desc':'Central Texas: Abilene, Sweetwater, Snyder, San Angelo (split from 915)'},
	'330':{'Region':'OH','dst':true,'time':'-5','desc':'NE Ohio: Akron, Canton, Youngstown; Mahoning County, parts of Trumbull/Warren counties (see splits 216, 440, overlay 234)'},
	'331':{'Region':'IL','dst':true,'time':'-6','desc':'W NE Illinois, western suburbs of Chicago (part of what used to be 708; overlaid on 630; eff 7/07)'},
	'334':{'Region':'AL','dst':true,'time':'-6','desc':'S Alabama: Auburn/Opelika, Montgomery and coastal areas (part of what used to be 205; see also 256, split 251)'},
	'336':{'Region':'NC','dst':true,'time':'-5','desc':'Cent. North Carolina: Greensboro, Winston-Salem, High Point (split from 910)'},
	'337':{'Region':'LA','dst':true,'time':'-6','desc':'SW Louisiana: Lake Charles, Lafayette (see split 318)'},
	'339':{'Region':'MA','dst':true,'time':'-5','desc':'Massachusetts: Boston suburbs, to the south and west (see splits 617, 508; overlaid on 781, eff 5/2/01)'},
	'340':{'Region':'VI','dst':false,'time':'-4','desc':'US Virgin Islands (see also 809)'},
	'341':{'Region':'CA','dst':true,'time':'-8','desc':'(overlay on 510; SUSPENDED)'},
	'345':{'Region':null,'dst':true,'time':'-5','desc':'Cayman Islands'},
	'347':{'Region':'NY','dst':true,'time':'-5','desc':'New York (overlay for 718: NYC area, except Manhattan)'},
	'351':{'Region':'MA','dst':true,'time':'-5','desc':'Massachusetts: north of Boston to NH, 508, and 781 (overlaid on 978, eff 4/2/01)'},
	'352':{'Region':'FL','dst':true,'time':'-5','desc':'Florida: Gainesville area, Ocala, Crystal River (split from 904)'},
	'360':{'Region':'WA','dst':true,'time':'-8','desc':'W Washington State: Olympia, Bellingham (area circling 206, 253, and 425; split from 206; see overlay 564)'},
	'361':{'Region':'TX','dst':true,'time':'-6','desc':'S Texas: Corpus Christi (split from 512; eff 2/13/99)'},
	'369':{'Region':'CA','dst':true,'time':'-8','desc':'Solano County (perm 12/2/00, mand 6/2/01)'},
	'380':{'Region':'OH','dst':true,'time':'-5','desc':'Ohio: Columbus (overlaid on 614; assigned but not in use)'},
	'385':{'Region':'UT','dst':true,'time':'-7','desc':'Utah: Salt Lake City Metro (split from 801, eff 3/30/02 POSTPONED; see also 435)'},
	'386':{'Region':'FL','dst':true,'time':'-5','desc':'N central Florida: Lake City (split from 904, perm 2/15/01, mand 11/5/01)'},
	'401':{'Region':'RI','dst':true,'time':'-5','desc':'Rhode Island'},
	'402':{'Region':'NE','dst':true,'time':'-6','desc':'E Nebraska: Omaha, Lincoln'},
	'403':{'Region':'AB','dst':true,'time':'-7','desc':'Canada: Southern Alberta (see 780, 867)'},
	'404':{'Region':'GA','dst':true,'time':'-5','desc':'N Georgia: Atlanta and suburbs (see overlay 678, split 770)'},
	'405':{'Region':'OK','dst':true,'time':'-6','desc':'W Oklahoma: Oklahoma City (see 580)'},
	'406':{'Region':'MT','dst':true,'time':'-7','desc':'Montana'},
	'407':{'Region':'FL','dst':true,'time':'-5','desc':'Central Florida: Metro Orlando (see overlay 689, eff 7/02; split 321)'},
	'408':{'Region':'CA','dst':true,'time':'-8','desc':'Cent. Coastal California: San Jose (see overlay 669)'},
	'409':{'Region':'TX','dst':true,'time':'-6','desc':'SE Texas: Galveston, Port Arthur, Beaumont (splits 936, 979)'},
	'410':{'Region':'MD','dst':true,'time':'-5','desc':'E Maryland: Baltimore, Annapolis, Chesapeake Bay area, Ocean City (see 443)'},
	'411':{'Region':null,'dst':null,'time':null,'desc':'Reserved for special applications'},
	'412':{'Region':'PA','dst':true,'time':'-5','desc':'W Pennsylvania: Pittsburgh (see split 724, overlay 878)'},
	'413':{'Region':'MA','dst':true,'time':'-5','desc':'W Massachusetts: Springfield'},
	'414':{'Region':'WI','dst':true,'time':'-6','desc':'SE Wisconsin: Milwaukee County (see splits 920, 262)'},
	'415':{'Region':'CA','dst':true,'time':'-8','desc':'California: San Francisco County and Marin County on the north side of the Golden Gate Bridge, extending north to Sonoma County (see 650)'},
	'416':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: S Cent. Ontario: Toronto (see overlay 647, eff 3/5/01)'},
	'417':{'Region':'MO','dst':true,'time':'-6','desc':'SW Missouri: Springfield'},
	'418':{'Region':'QC','dst':true,'time':'-5/-4','desc':'Canada: NE Quebec: Quebec'},
	'419':{'Region':'OH','dst':true,'time':'-5','desc':'NW Ohio: Toledo (see overlay 567, perm 1/1/02)'},
	'423':{'Region':'TN','dst':true,'time':'-5','desc':'E Tennessee, except Knoxville metro area: Chattanooga, Bristol, Johnson City, Kingsport, Greeneville (see split 865; part of what used to be 615)'},
	'424':{'Region':'CA','dst':true,'time':'-8','desc':'S California: Los Angeles (see split 562; overlaid on 310 mand 7/26/06)'},
	'425':{'Region':'WA','dst':true,'time':'-8','desc':'Washington: North Tier - Everett, Bellevue (split from 206, see also 253; overlay 564)'},
	'430':{'Region':'TX','dst':true,'time':'-6','desc':'NE Texas: Tyler (overlaid on 903, eff 7/20/02)'},
	'432':{'Region':'TX','dst':true,'time':'-7/-6','desc':'W Texas: Big Spring, Midland, Odessa (split from 915, eff 4/5/03)'},
	'434':{'Region':'VA','dst':true,'time':'-5','desc':'E Virginia: Charlottesville, Lynchburg, Danville, South Boston, and Emporia (split from 804, eff 6/1/01; see also 757)'},
	'435':{'Region':'UT','dst':true,'time':'-7','desc':'Rural Utah outside Salt Lake City metro (see split 801)'},
	'438':{'Region':'QC','dst':true,'time':'-5','desc':'Canada: SW Quebec: Montreal city (overlaid on 514, [delayed until 6/06] eff 10/10/03, mand 2/7/04)'},
	'440':{'Region':'OH','dst':true,'time':'-5','desc':'Ohio: Cleveland metro area, excluding Cleveland (split from 216, see also 330)'},
	'441':{'Region':null,'dst':true,'time':'-4','desc':'Bermuda (part of what used to be 809)'},
	'442':{'Region':'CA','dst':true,'time':'-8','desc':'Far north suburbs of San Diego (Oceanside, Escondido, SUSPENDED -- originally perm 10/21/00, mand 4/14/01)'},
	'443':{'Region':'MD','dst':true,'time':'-5','desc':'E Maryland: Baltimore, Annapolis, Chesapeake Bay area, Ocean City (overlaid on 410)'},
	'450':{'Region':'QC','dst':true,'time':'-5/-4','desc':'Canada: Southeastern Quebec; suburbs outside metro Montreal'},
	'456':{'Region':null,'dst':null,'time':null,'desc':'Inbound International'},
	'464':{'Region':'IL','dst':true,'time':'-6','desc':'Illinois: south suburbs of Chicago (see 630; overlaid on 708)'},
	'469':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: Dallas Metro (overlays 214/972)'},
	'470':{'Region':'GA','dst':true,'time':'-5','desc':'Georgia: Greater Atlanta Metropolitan Area (overlaid on 404/770/678; mand 9/2/01)'},
	'473':{'Region':null,'dst':true,'time':'-4','desc':'Grenada ("new" -- split from 809)'},
	'475':{'Region':'CT','dst':true,'time':'-5','desc':'Connecticut: New Haven, Greenwich, southwestern (postponed; was perm 1/6/01; mand 3/1/01???)'},
	'478':{'Region':'GA','dst':true,'time':'-5','desc':'Central Georgia: Macon (split from 912; see also 229; perm 8/1/00; mand 8/1/01)'},
	'479':{'Region':'AR','dst':true,'time':'-6','desc':'NW Arkansas: Fort Smith, Fayetteville, Springdale, Bentonville (SPLIt from 501, perm 1/19/02, mand 7/20/02)'},
	'480':{'Region':'AZ','dst':false,'time':'-7','desc':'Arizona: East Phoenix (see 520; also Phoenix split 602, 623)'},
	'484':{'Region':'PA','dst':true,'time':'-5','desc':'SE Pennsylvania: Allentown, Bethlehem, Reading, West Chester, Norristown (see 610)'},
	'500':{'Region':null,'dst':null,'time':null,'desc':'Personal Communication Service'},
	'501':{'Region':'AR','dst':true,'time':'-6','desc':'Central Arkansas: Little Rock, Hot Springs, Conway (see split 479)'},
	'502':{'Region':'KY','dst':true,'time':'-5','desc':'N Central Kentucky: Louisville (see 270)'},
	'503':{'Region':'OR','dst':true,'time':'-8','desc':'Oregon (see 541, 971)'},
	'504':{'Region':'LA','dst':true,'time':'-6','desc':'E Louisiana: New Orleans metro area (see splits 225, 985)'},
	'505':{'Region':'NM','dst':true,'time':'-7','desc':'North central and northwestern New Mexico (Albuquerque, Santa Fe, Los Alamos; see split 575, eff 10/07/07)'},
	'506':{'Region':'NB','dst':true,'time':'-4','desc':'Canada: New Brunswick'},
	'507':{'Region':'MN','dst':true,'time':'-6','desc':'S Minnesota: Rochester, Mankato, Worthington'},
	'508':{'Region':'MA','dst':true,'time':'-5','desc':'Cent. Massachusetts: Framingham; Cape Cod (see split 978, overlay 774)'},
	'509':{'Region':'WA','dst':true,'time':'-8','desc':'E and Central Washington state: Spokane, Yakima, Walla Walla, Ellensburg'},
	'510':{'Region':'CA','dst':true,'time':'-8','desc':'California: Oakland, East Bay (see 925)'},
	'511':{'Region':null,'dst':null,'time':null,'desc':'Nationwide travel information'},
	'512':{'Region':'TX','dst':true,'time':'-6','desc':'S Texas: Austin (see split 361; overlay 737, perm 11/10/01)'},
	'513':{'Region':'OH','dst':true,'time':'-5','desc':'SW Ohio: Cincinnati (see split 937; overlay 283 cancelled)'},
	'514':{'Region':'QC','dst':true,'time':'-5','desc':'Canada: SW Quebec: Montreal city (see overlay 438, eff 10/10/03, mand 2/7/04)'},
	'515':{'Region':'IA','dst':true,'time':'-6','desc':'Cent. Iowa: Des Moines (see split 641)'},
	'516':{'Region':'NY','dst':true,'time':'-5','desc':'New York: Nassau County, Long Island; Hempstead (see split 631)'},
	'517':{'Region':'MI','dst':true,'time':'-5','desc':'Cent. Michigan: Lansing (see split 989)'},
	'518':{'Region':'NY','dst':true,'time':'-5','desc':'NE New York: Albany'},
	'519':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: SW Ontario: Windsor (see overlay 226)'},
	'520':{'Region':'AZ','dst':false,'time':'-7','desc':'SE Arizona: Tucson area (split from 602; see split 928)'},
	'530':{'Region':'CA','dst':true,'time':'-8','desc':'NE California: Eldorado County area, excluding Eldorado Hills itself: incl cities of Auburn, Chico, Redding, So. Lake Tahoe, Marysville, Nevada City/Grass Valley (split from 916)'},
	'540':{'Region':'VA','dst':true,'time':'-5','desc':'Western and Southwest Virginia: Shenandoah and Roanoke valleys: Fredericksburg, Harrisonburg, Roanoke, Salem, Lexington and nearby areas (see split 276; split from 703)'},
	'541':{'Region':'OR','dst':true,'time':'-8/-7','desc':'Oregon: Eugene, Medford (split from 503; 503 retains NW part [Portland/Salem], all else moves to 541; eastern oregon is UTC-7)'},
	'551':{'Region':'NJ','dst':true,'time':'-5','desc':'N New Jersey: Jersey City, Hackensack (overlaid on 201)'},
	'555':{'Region':null,'dst':null,'time':null,'desc':'Reserved for directory assistance applications'},
	'557':{'Region':'MO','dst':true,'time':'-6','desc':'SE Missouri: St Louis metro area only (cancelled: overlaid on 314)'},
	'559':{'Region':'CA','dst':true,'time':'-8','desc':'Central California: Fresno (split from 209)'},
	'561':{'Region':'FL','dst':true,'time':'-5','desc':'S. Central Florida: Palm Beach County (West Palm Beach, Boca Raton, Vero Beach; see split 772, eff 2/11/02; mand 11/11/02)'},
	'562':{'Region':'CA','dst':true,'time':'-8','desc':'California: Long Beach (split from 310 Los Angeles)'},
	'563':{'Region':'IA','dst':true,'time':'-6','desc':'E Iowa: Davenport, Dubuque (split from 319, eff 3/25/01)'},
	'564':{'Region':'WA','dst':true,'time':'-8','desc':'W Washington State: Olympia, Bellingham (overlaid on 360; see also 206, 253, 425; assigned but not in use)'},
	'567':{'Region':'OH','dst':true,'time':'-5','desc':'NW Ohio: Toledo (overlaid on 419, perm 1/1/02)'},
	'570':{'Region':'PA','dst':true,'time':'-5','desc':'NE and N Central Pennsylvania: Wilkes-Barre, Scranton (see 717)'},
	'571':{'Region':'VA','dst':true,'time':'-5','desc':'Northern Virginia: Arlington, McLean, Tysons Corner (to be overlaid on 703 3/1/00; see earlier split 540)'},
	'573':{'Region':'MO','dst':true,'time':'-6','desc':'SE Missouri: excluding St Louis metro area, includes Central/East Missouri, area between St. Louis and Kansas City'},
	'574':{'Region':'IN','dst':true,'time':'-5','desc':'N Indiana: Elkhart, South Bend (split from 219)'},
	'575':{'Region':'NM','dst':true,'time':'-7','desc':'New Mexico (Las Cruces, Alamogordo, Roswell; split from 505, eff 10/07/07)'},
	'580':{'Region':'OK','dst':true,'time':'-6','desc':'W Oklahoma (rural areas outside Oklahoma City; split from 405)'},
	'585':{'Region':'NY','dst':true,'time':'-5','desc':'NW New York: Rochester (split from 716)'},
	'586':{'Region':'MI','dst':true,'time':'-5','desc':'Michigan: Macomb County (split from 810; perm 9/22/01, mand 3/23/02)'},
	'600':{'Region':null,'dst':null,'time':null,'desc':'Canadian Services'},
	'601':{'Region':'MS','dst':true,'time':'-6','desc':'Mississippi: Meridian, Jackson area (see splits 228, 662; overlay 769)'},
	'602':{'Region':'AZ','dst':false,'time':'-7','desc':'Arizona: Phoenix (see 520; also Phoenix split 480, 623)'},
	'603':{'Region':'NH','dst':true,'time':'-5','desc':'New Hampshire'},
	'604':{'Region':'BC','dst':true,'time':'-8','desc':'Canada: British Columbia: Greater Vancouver (overlay 778, perm 11/3/01; see 250)'},
	'605':{'Region':'SD','dst':true,'time':'-6/-7','desc':'South Dakota'},
	'606':{'Region':'KY','dst':true,'time':'-5/-6','desc':'E Kentucky: area east of Frankfort: Ashland (see 859)'},
	'607':{'Region':'NY','dst':true,'time':'-5','desc':'S Cent. New York: Ithaca, Binghamton; Catskills'},
	'608':{'Region':'WI','dst':true,'time':'-6','desc':'SW Wisconsin: Madison'},
	'609':{'Region':'NJ','dst':true,'time':'-5','desc':'S New Jersey: Trenton (see 856)'},
	'610':{'Region':'PA','dst':true,'time':'-5','desc':'SE Pennsylvania: Allentown, Bethlehem, Reading, West Chester, Norristown (see overlays 484, 835)'},
	'611':{'Region':null,'dst':null,'time':null,'desc':'Reserved for special applications'},
	'612':{'Region':'MN','dst':true,'time':'-6','desc':'Cent. Minnesota: Minneapolis (split from St. Paul, see 651; see splits 763, 952)'},
	'613':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: SE Ontario: Ottawa'},
	'614':{'Region':'OH','dst':true,'time':'-5','desc':'SE Ohio: Columbus (see overlay 380)'},
	'615':{'Region':'TN','dst':true,'time':'-6','desc':'Northern Middle Tennessee: Nashville metro area (see 423, 931)'},
	'616':{'Region':'MI','dst':true,'time':'-5','desc':'W Michigan: Holland, Grand Haven, Greenville, Grand Rapids, Ionia (see split 269)'},
	'617':{'Region':'MA','dst':true,'time':'-5','desc':'Massachusetts: greater Boston (see overlay 857)'},
	'618':{'Region':'IL','dst':true,'time':'-6','desc':'S Illinois: Centralia'},
	'619':{'Region':'CA','dst':true,'time':'-8','desc':'S California: San Diego (see split 760; overlay 858, 935)'},
	'620':{'Region':'KS','dst':true,'time':'-6','desc':'S Kansas: Wichita (split from 316; perm 2/3/01)'},
	'623':{'Region':'AZ','dst':false,'time':'-7','desc':'Arizona: West Phoenix (see 520; also Phoenix split 480, 602)'},
	'626':{'Region':'CA','dst':true,'time':'-8','desc':'E S California: Pasadena (split from 818 Los Angeles)'},
	'627':{'Region':'CA','dst':true,'time':'-8','desc':'No longer in use [was Napa, Sonoma counties (perm 10/13/01, mand 4/13/02); now 707]'},
	'628':{'Region':'CA','dst':true,'time':'-8','desc':'(Region unknown; perm 10/21/00)'},
	'630':{'Region':'IL','dst':true,'time':'-6','desc':'W NE Illinois, western suburbs of Chicago (part of what used to be 708; overlay 331)'},
	'631':{'Region':'NY','dst':true,'time':'-5','desc':'New York: Suffolk County, Long Island; Huntington, Riverhead (split 516)'},
	'636':{'Region':'MO','dst':true,'time':'-6','desc':'Missouri: W St. Louis metro area of St. Louis county, St. Charles County, Jefferson County area south (between 314 and 573)'},
	'641':{'Region':'IA','dst':true,'time':'-6','desc':'Iowa: Mason City, Marshalltown, Creston, Ottumwa (split from 515; perm 7/9/00)'},
	'646':{'Region':'NY','dst':true,'time':'-5','desc':'New York (overlay 212/917) NYC: Manhattan only'},
	'647':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: S Cent. Ontario: Toronto (overlaid on 416; eff 3/5/01)'},
	'649':{'Region':null,'dst':true,'time':'-5','desc':'Turks & Caicos Islands'},
	'650':{'Region':'CA','dst':true,'time':'-8','desc':'California: Peninsula south of San Francisco -- San Mateo County, parts of Santa Clara County (split from 415)'},
	'651':{'Region':'MN','dst':true,'time':'-6','desc':'Cent. Minnesota: St. Paul (split from Minneapolis, see 612)'},
	'660':{'Region':'MO','dst':true,'time':'-6','desc':'N Missouri (split from 816)'},
	'661':{'Region':'CA','dst':true,'time':'-8','desc':'California: N Los Angeles, Mckittrick, Mojave, Newhall, Oildale, Palmdale, Taft, Tehachapi, Bakersfield, Earlimart, Lancaster (split from 805)'},
	'662':{'Region':'MS','dst':true,'time':'-6','desc':'N Mississippi: Tupelo, Grenada (split from 601)'},
	'664':{'Region':null,'dst':true,'time':'-4','desc':'Montserrat (split from 809)'},
	'669':{'Region':'CA','dst':true,'time':'-8','desc':'Cent. Coastal California: San Jose (rejected was: overlaid on 408)'},
	'670':{'Region':'MP','dst':false,'time':'+10','desc':'Commonwealth of the Northern Mariana Islands (CNMI, US Commonwealth)'},
	'671':{'Region':'GU','dst':false,'time':'+10','desc':'Guam'},
	'678':{'Region':'GA','dst':true,'time':'-5','desc':'N Georgia: metropolitan Atlanta (overlay; see 404, 770)'},
	'679':{'Region':'MI','dst':true,'time':'-5/-6','desc':'Michigan: Dearborn area (overlaid on 313; assigned but not in use)'},
	'682':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: Fort Worth areas (perm 10/7/00, mand 12/9/00)'},
	'684':{'Region':null,'dst':true,'time':'-11','desc':'American Samoa'},
	'689':{'Region':'FL','dst':true,'time':'-5','desc':'Central Florida: Metro Orlando (see overlay 321; overlaid on 407, assigned but not in use)'},
	'700':{'Region':null,'dst':null,'time':null,'desc':'Interexchange Carrier Services'},
	'701':{'Region':'ND','dst':true,'time':'-6','desc':'North Dakota'},
	'702':{'Region':'NV','dst':true,'time':'-8','desc':'S. Nevada: Clark County, incl Las Vegas (see 775)'},
	'703':{'Region':'VA','dst':true,'time':'-5','desc':'Northern Virginia: Arlington, McLean, Tysons Corner (see split 540; overlay 571)'},
	'704':{'Region':'NC','dst':true,'time':'-5','desc':'W North Carolina: Charlotte (see split 828, overlay 980)'},
	'705':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: NE Ontario: Sault Ste. Marie/N Ontario: N Bay, Sudbury'},
	'706':{'Region':'GA','dst':true,'time':'-5','desc':'N Georgia: Columbus, Augusta (see overlay 762)'},
	'707':{'Region':'CA','dst':true,'time':'-8','desc':'NW California: Santa Rosa, Napa, Vallejo, American Canyon, Fairfield'},
	'708':{'Region':'IL','dst':true,'time':'-6','desc':'Illinois: southern and western suburbs of Chicago (see 630; overlay 464)'},
	'709':{'Region':'NL','dst':true,'time':'-4/-3.5','desc':'Canada: Newfoundland and Labrador'},
	'710':{'Region':null,'dst':null,'time':null,'desc':'US Government'},
	'711':{'Region':null,'dst':null,'time':null,'desc':'Telecommunications Relay Services'},
	'712':{'Region':'IA','dst':true,'time':'-6','desc':'W Iowa: Council Bluffs'},
	'713':{'Region':'TX','dst':true,'time':'-6','desc':'Mid SE Texas: central Houston (split, 281; overlay 832)'},
	'714':{'Region':'CA','dst':true,'time':'-8','desc':'North and Central Orange County (see split 949)'},
	'715':{'Region':'WI','dst':true,'time':'-6','desc':'N Wisconsin: Eau Claire, Wausau, Superior'},
	'716':{'Region':'NY','dst':true,'time':'-5','desc':'NW New York: Buffalo (see split 585)'},
	'717':{'Region':'PA','dst':true,'time':'-5','desc':'E Pennsylvania: Harrisburg (see split 570)'},
	'718':{'Region':'NY','dst':true,'time':'-5','desc':'New York City, New York (Queens, Staten Island, The Bronx, and Brooklyn; see 212, 347)'},
	'719':{'Region':'CO','dst':true,'time':'-7','desc':'SE Colorado: Pueblo, Colorado Springs'},
	'720':{'Region':'CO','dst':true,'time':'-7','desc':'Central Colorado: Denver (overlaid on 303)'},
	'724':{'Region':'PA','dst':true,'time':'-5','desc':'SW Pennsylvania (areas outside metro Pittsburgh; split from 412)'},
	'727':{'Region':'FL','dst':true,'time':'-5','desc':'Florida Tampa Metro: Saint Petersburg, Clearwater (Pinellas and parts of Pasco County; split from 813)'},
	'731':{'Region':'TN','dst':true,'time':'-6','desc':'W Tennessee: outside Memphis metro area (split from 901, perm 2/12/01, mand 9/17/01)'},
	'732':{'Region':'NJ','dst':true,'time':'-5','desc':'Cent. New Jersey: Toms River, New Brunswick, Bound Brook (see overlay 848)'},
	'734':{'Region':'MI','dst':true,'time':'-5','desc':'SE Michigan: west and south of Detroit -- Ann Arbor, Monroe (split from 313)'},
	'737':{'Region':'TX','dst':true,'time':'-6','desc':'S Texas: Austin (overlaid on 512, suspended; see also 361)'},
	'740':{'Region':'OH','dst':true,'time':'-5','desc':'SE Ohio (rural areas outside Columbus; split from 614)'},
	'747':{'Region':'CA','dst':true,'time':'-8','desc':'S California: Los Angeles, Agoura Hills, Calabasas, Hidden Hills, and Westlake Village (see 818; implementation suspended)'},
	'754':{'Region':'FL','dst':true,'time':'-5','desc':'Florida: Broward County area, incl Ft. Lauderdale (overlaid on 954; perm 8/1/01, mand 9/1/01)'},
	'757':{'Region':'VA','dst':true,'time':'-5','desc':'E Virginia: Tidewater / Hampton Roads area -- Norfolk, Virginia Beach, Chesapeake, Portsmouth, Hampton, Newport News, Suffolk (part of what used to be 804)'},
	'758':{'Region':null,'dst':true,'time':'-4','desc':'St. Lucia (split from 809)'},
	'760':{'Region':'CA','dst':true,'time':'-8','desc':'California: San Diego North County to Sierra Nevada (split from 619)'},
	'762':{'Region':'GA','dst':true,'time':'-5','desc':'N Georgia: Columbus, Augusta (overlaid on 706)'},
	'763':{'Region':'MN','dst':true,'time':'-6','desc':'Minnesota: Minneapolis NW (split from 612; see also 952)'},
	'764':{'Region':'CA','dst':true,'time':'-8','desc':'(overlay on 650; SUSPENDED)'},
	'765':{'Region':'IN','dst':true,'time':'-5','desc':'Indiana: outside Indianapolis (split from 317)'},
	'767':{'Region':null,'dst':true,'time':'-4','desc':'Dominica (split from 809)'},
	'769':{'Region':'MS','dst':true,'time':'-6','desc':'Mississippi: Meridian, Jackson area (overlaid on 601; perm 7/19/04, mand 3/14/05)'},
	'770':{'Region':'GA','dst':true,'time':'-5','desc':'Georgia: Atlanta suburbs: outside of I-285 ring road (part of what used to be 404; see also overlay 678)'},
	'772':{'Region':'FL','dst':true,'time':'-5','desc':'S. Central Florida: St. Lucie, Martin, and Indian River counties (split from 561; eff 2/11/02; mand 11/11/02)'},
	'773':{'Region':'IL','dst':true,'time':'-6','desc':'Illinois: city of Chicago, outside the loop (see 312; overlay 872)'},
	'774':{'Region':'MA','dst':true,'time':'-5','desc':'Cent. Massachusetts: Framingham; Cape Cod (see split 978, overlaid on 508, eff 4/2/01)'},
	'775':{'Region':'NV','dst':true,'time':'-8','desc':'N. Nevada: Reno (all of NV except Clark County area; see 702)'},
	'778':{'Region':'BC','dst':true,'time':'-8','desc':'Canada: British Columbia: Greater Vancouver (overlaid on 604, per 11/3/01; see also 250)'},
	'779':{'Region':'IL','dst':true,'time':'-6','desc':'NW Illinois: Rockford, Kankakee (overlaid on 815; eff 8/19/06, mand 2/17/07)'},
	'780':{'Region':'AB','dst':true,'time':'-7','desc':'Canada: Northern Alberta, north of Lacombe (see 403)'},
	'781':{'Region':'MA','dst':true,'time':'-5','desc':'Massachusetts: Boston surburbs, to the north and west (see splits 617, 508; overlay 339)'},
	'784':{'Region':null,'dst':true,'time':'-4','desc':'St. Vincent & Grenadines (split from 809)'},
	'785':{'Region':'KS','dst':true,'time':'-6','desc':'N & W Kansas: Topeka (split from 913)'},
	'786':{'Region':'FL','dst':true,'time':'-5','desc':'SE Florida, Monroe County (Miami; overlaid on 305)'},
	'787':{'Region':'PR','dst':false,'time':'-4','desc':'Puerto Rico (see overlay 939, perm 8/1/01)'},
	'800':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free (see 888, 877, 866, 855, 844, 833, 822)'},
	'801':{'Region':'UT','dst':true,'time':'-7','desc':'Utah: Salt Lake City Metro (see split 385, eff 3/30/02; see also split 435)'},
	'802':{'Region':'VT','dst':true,'time':'-5','desc':'Vermont'},
	'803':{'Region':'SC','dst':true,'time':'-5','desc':'South Carolina: Columbia, Aiken, Sumter (see 843, 864)'},
	'804':{'Region':'VA','dst':true,'time':'-5','desc':'E Virginia: Richmond (see splits 757, 434)'},
	'805':{'Region':'CA','dst':true,'time':'-8','desc':'S Cent. and Cent. Coastal California: Ventura County, Santa Barbara County: San Luis Obispo, Thousand Oaks, Carpinteria, Santa Barbara, Santa Maria, Lompoc, Santa Ynez Valley / Solvang (see 661 split)'},
	'806':{'Region':'TX','dst':true,'time':'-6','desc':'Panhandle Texas: Amarillo, Lubbock'},
	'807':{'Region':'ON','dst':true,'time':'-5/-6','desc':'Canada: W Ontario: Thunder Bay region to Manitoba border'},
	'808':{'Region':'HI','dst':false,'time':'-10','desc':'Hawaii'},
	'809':{'Region':null,'dst':true,'time':'-4','desc':'Dominican Republic (see splits 264, 268, 284, 340, 441, 473, 664, 758, 767, 784, 868, 876; overlay 829)'},
	'810':{'Region':'MI','dst':true,'time':'-5','desc':'E Michigan: Flint, Pontiac (see 248; split 586)'},
	'811':{'Region':null,'dst':null,'time':null,'desc':'Reserved for special applications'},
	'812':{'Region':'IN','dst':true,'time':'-6/-5','desc':'S Indiana: Evansville, Cincinnati outskirts in IN, Columbus, Bloomington (mostly GMT-5)'},
	'813':{'Region':'FL','dst':true,'time':'-5','desc':'SW Florida: Tampa Metro (splits 727 St. Petersburg, Clearwater, and 941 Sarasota)'},
	'814':{'Region':'PA','dst':true,'time':'-5','desc':'Cent. Pennsylvania: Erie'},
	'815':{'Region':'IL','dst':true,'time':'-6','desc':'NW Illinois: Rockford, Kankakee (see overlay 779; eff 8/19/06, mand 2/17/07)'},
	'816':{'Region':'MO','dst':true,'time':'-6','desc':'N Missouri: Kansas City (see split 660, overlay 975)'},
	'817':{'Region':'TX','dst':true,'time':'-6','desc':'N Cent. Texas: Fort Worth area (see 254, 940)'},
	'818':{'Region':'CA','dst':true,'time':'-8','desc':'S California: Los Angeles: San Fernando Valley (see 213, 310, 562, 626, 747)'},
	'819':{'Region':'QC','dst':true,'time':'-5','desc':'NW Quebec: Trois Rivieres, Sherbrooke, Outaouais (Gatineau, Hull), and the Laurentians (up to St Jovite / Tremblant) (see 867)'},
	'822':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free (proposed, may not be in use yet)'},
	'828':{'Region':'NC','dst':true,'time':'-5','desc':'W North Carolina: Asheville (split from 704)'},
	'829':{'Region':null,'dst':true,'time':'-4','desc':'Dominican Republic (perm 1/31/05; mand 8/1/05; overlaid on 809)'},
	'830':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: region surrounding San Antonio (split from 210)'},
	'831':{'Region':'CA','dst':true,'time':'-8','desc':'California: central coast area from Santa Cruz through Monterey County'},
	'832':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: Houston (overlay 713/281)'},
	'833':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free (proposed, may not be in use yet)'},
	'835':{'Region':'PA','dst':true,'time':'-5','desc':'SE Pennsylvania: Allentown, Bethlehem, Reading, West Chester, Norristown (overlaid on 610, eff 5/1/01; see also 484)'},
	'843':{'Region':'SC','dst':true,'time':'-5','desc':'South Carolina, coastal area: Charleston, Beaufort, Myrtle Beach (split from 803)'},
	'844':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free (proposed, may not be in use yet)'},
	'845':{'Region':'NY','dst':true,'time':'-5','desc':'New York: Poughkeepsie; Nyack, Nanuet, Valley Cottage, New City, Putnam, Dutchess, Rockland, Orange, Ulster and parts of Sullivan counties in New York\'s lower Hudson Valley and Delaware County in the Catskills (see 914; perm 6/5/00)'},
	'847':{'Region':'IL','dst':true,'time':'-6','desc':'Northern NE Illinois: northwestern suburbs of chicago (Evanston, Waukegan, Northbrook; see overlay 224)'},
	'848':{'Region':'NJ','dst':true,'time':'-5','desc':'Cent. New Jersey: Toms River, New Brunswick, Bound Brook (see overlay 732)'},
	'850':{'Region':'FL','dst':true,'time':'-6/-5','desc':'Florida panhandle, from east of Tallahassee to Pensacola (split from 904); western panhandle (Pensacola, Panama City) are UTC-6'},
	'855':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free (proposed, may not be in use yet)'},
	'856':{'Region':'NJ','dst':true,'time':'-5','desc':'SW New Jersey: greater Camden area, Mt Laurel (split from 609)'},
	'857':{'Region':'MA','dst':true,'time':'-5','desc':'Massachusetts: greater Boston (overlaid on 617, eff 4/2/01)'},
	'858':{'Region':'CA','dst':true,'time':'-8','desc':'S California: San Diego (see split 760; overlay 619, 935)'},
	'859':{'Region':'KY','dst':true,'time':'-5','desc':'N and Central Kentucky: Lexington; suburban KY counties of Cincinnati OH metro area; Covington, Newport, Ft. Thomas, Ft. Wright, Florence (split from 606)'},
	'860':{'Region':'CT','dst':true,'time':'-5','desc':'Connecticut: areas outside of Fairfield and New Haven Counties (split from 203, overlay 959)'},
	'862':{'Region':'NJ','dst':true,'time':'-5','desc':'N New Jersey: Newark Paterson Morristown (overlaid on 973)'},
	'863':{'Region':'FL','dst':true,'time':'-5','desc':'Florida: Lakeland, Polk County (split from 941)'},
	'864':{'Region':'SC','dst':true,'time':'-5','desc':'South Carolina, upstate area: Greenville, Spartanburg (split from 803)'},
	'865':{'Region':'TN','dst':true,'time':'-5','desc':'E Tennessee: Knoxville, Knox and adjacent counties (split from 423; part of what used to be 615)'},
	'866':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free'},
	'867':{'Region':'YT','dst':true,'time':'-5/-6/-7/-8','desc':'Canada: Yukon, Northwest Territories, Nunavut (split from 403/819)'},
	'868':{'Region':null,'dst':true,'time':'-4','desc':'Trinidad and Tobago ("new" -- see 809)'},
	'869':{'Region':null,'dst':true,'time':'-4','desc':'St. Kitts & Nevis'},
	'870':{'Region':'AR','dst':true,'time':'-6','desc':'Arkansas: areas outside of west/central AR: Jonesboro, etc'},
	'872':{'Region':'IL','dst':true,'time':'-6','desc':'Illinois: Chicago (downtown only -- in the loop; see 773; overlaid on 312 and 773)'},
	'876':{'Region':null,'dst':true,'time':'-5','desc':'Jamaica (split from 809)'},
	'877':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free'},
	'878':{'Region':'PA','dst':true,'time':'-5','desc':'Pittsburgh, New Castle (overlaid on 412, perm 8/17/01, mand t.b.a.)'},
	'880':{'Region':null,'dst':null,'time':null,'desc':'Paid Toll-Free Service'},
	'881':{'Region':null,'dst':null,'time':null,'desc':'Paid Toll-Free Service'},
	'882':{'Region':null,'dst':null,'time':null,'desc':'Paid Toll-Free Service'},
	'888':{'Region':null,'dst':null,'time':null,'desc':'US/Canada toll free'},
	'898':{'Region':null,'dst':null,'time':null,'desc':'VoIP service'},
	'900':{'Region':null,'dst':null,'time':null,'desc':'US toll calls -- prices vary with the number called'},
	'901':{'Region':'TN','dst':true,'time':'-6','desc':'W Tennessee: Memphis metro area (see 615, 931, split 731)'},
	'902':{'Region':'NS','dst':true,'time':'-4','desc':'Canada: Nova Scotia, Prince Edward Island'},
	'903':{'Region':'TX','dst':true,'time':'-6','desc':'NE Texas: Tyler (see overlay 430, eff 7/20/02)'},
	'904':{'Region':'FL','dst':true,'time':'-5','desc':'N Florida: Jacksonville (see splits 352, 386, 850)'},
	'905':{'Region':'ON','dst':true,'time':'-5','desc':'Canada: S Cent. Ontario: Greater Toronto Area -- Durham, Halton, Hamilton-Wentworth, Niagara, Peel, York, and southern Simcoe County (excluding Toronto -- see overlay 289 [eff 6/9/01], splits 416, 647)'},
	'906':{'Region':'MI','dst':true,'time':'-6/-5','desc':'Upper Peninsula Michigan: Sault Ste. Marie, Escanaba, Marquette (UTC-6 towards the WI border)'},
	'907':{'Region':'AK','dst':true,'time':'-9','desc':'Alaska'},
	'908':{'Region':'NJ','dst':true,'time':'-5','desc':'Cent. New Jersey: Elizabeth, Basking Ridge, Somerville, Bridgewater, Bound Brook'},
	'909':{'Region':'CA','dst':true,'time':'-8','desc':'California: Inland empire: San Bernardino (see split 951), Riverside'},
	'910':{'Region':'NC','dst':true,'time':'-5','desc':'S Cent. North Carolina: Fayetteville, Wilmington (see 336)'},
	'911':{'Region':null,'dst':null,'time':null,'desc':'Emergency'},
	'912':{'Region':'GA','dst':true,'time':'-5','desc':'SE Georgia: Savannah (see splits 229, 478)'},
	'913':{'Region':'KS','dst':true,'time':'-6','desc':'Kansas: Kansas City area (see 785)'},
	'914':{'Region':'NY','dst':true,'time':'-5','desc':'S New York: Westchester County (see 845)'},
	'915':{'Region':'TX','dst':true,'time':'-7/-6','desc':'W Texas: El Paso (see splits 325 eff 4/5/03; 432, eff 4/5/03)'},
	'916':{'Region':'CA','dst':true,'time':'-8','desc':'NE California: Sacramento, Walnut Grove, Lincoln, Newcastle and El Dorado Hills (split to 530)'},
	'917':{'Region':'NY','dst':true,'time':'-5','desc':'New York: New York City (cellular, see 646)'},
	'918':{'Region':'OK','dst':true,'time':'-6','desc':'E Oklahoma: Tulsa'},
	'919':{'Region':'NC','dst':true,'time':'-5','desc':'E North Carolina: Raleigh (see split 252, overlay 984)'},
	'920':{'Region':'WI','dst':true,'time':'-6','desc':'NE Wisconsin: Appleton, Green Bay, Sheboygan, Fond du Lac (from Beaver Dam NE to Oshkosh, Appleton, and Door County; part of what used to be 414)'},
	'925':{'Region':'CA','dst':true,'time':'-8','desc':'California: Contra Costa area: Antioch, Concord, Pleasanton, Walnut Creek (split from 510)'},
	'927':{'Region':'FL','dst':true,'time':'-5','desc':'Florida: Cellular coverage in Orlando area'},
	'928':{'Region':'AZ','dst':false,'time':'-7','desc':'Central and Northern Arizona: Prescott, Flagstaff, Yuma (split from 520)'},
	'931':{'Region':'TN','dst':true,'time':'-6','desc':'Middle Tennessee: semi-circular ring around Nashville (split from 615)'},
	'935':{'Region':'CA','dst':true,'time':'-8','desc':'S California: San Diego (see split 760; overlay 858, 619; assigned but not in use)'},
	'936':{'Region':'TX','dst':true,'time':'-6','desc':'SE Texas: Conroe, Lufkin, Nacogdoches, Crockett (split from 409, see also 979)'},
	'937':{'Region':'OH','dst':true,'time':'-5','desc':'SW Ohio: Dayton (part of what used to be 513)'},
	'939':{'Region':'PR','dst':false,'time':'-4','desc':'Puerto Rico (overlaid on 787, perm 8/1/01)'},
	'940':{'Region':'TX','dst':true,'time':'-6','desc':'N Cent. Texas: Denton, Wichita Falls (split from 254, 817)'},
	'941':{'Region':'FL','dst':true,'time':'-5','desc':'SW Florida: Sarasota and Manatee counties (part of what used to be 813; see split 863)'},
	'947':{'Region':'MI','dst':true,'time':'-5/-6','desc':'Michigan: Oakland County (overlays 248, perm 5/5/01)'},
	'949':{'Region':'CA','dst':true,'time':'-8','desc':'California: S Coastal Orange County (split from 714)'},
	'951':{'Region':'CA','dst':true,'time':'-8','desc':'California: W Riverside County (split from 909; eff 7/17/04)'},
	'952':{'Region':'MN','dst':true,'time':'-6','desc':'Minnesota: Minneapolis SW, Bloomington (split from 612; see also 763)'},
	'954':{'Region':'FL','dst':true,'time':'-5','desc':'Florida: Broward County area, incl Ft. Lauderdale (part of what used to be 305, see overlay 754)'},
	'956':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: Valley of Texas area; Harlingen, Laredo (split from 210)'},
	'957':{'Region':'NM','dst':true,'time':'-7','desc':'New Mexico (pending; region unknown)'},
	'959':{'Region':'CT','dst':true,'time':'-5','desc':'Connecticut: Hartford, New London (postponed; was overlaid on 860 perm 1/6/01; mand 3/1/01???)'},
	'970':{'Region':'CO','dst':true,'time':'-7','desc':'N and W Colorado (part of what used to be 303)'},
	'971':{'Region':'OR','dst':true,'time':'-8','desc':'Oregon: Metropolitan Portland, Salem/Keizer area, incl Cricket Wireless (see 503; perm 10/1/00)'},
	'972':{'Region':'TX','dst':true,'time':'-6','desc':'Texas: Dallas Metro (overlays 214/469)'},
	'973':{'Region':'NJ','dst':true,'time':'-5','desc':'N New Jersey: Newark, Paterson, Morristown (see overlay 862; split from 201)'},
	'975':{'Region':'MO','dst':true,'time':'-6','desc':'N Missouri: Kansas City (overlaid on 816)'},
	'976':{'Region':null,'dst':null,'time':null,'desc':'Unassigned'},
	'978':{'Region':'MA','dst':true,'time':'-5','desc':'Massachusetts: north of Boston to NH (see split 978 -- this is the northern half of old 508; see overlay 351)'},
	'979':{'Region':'TX','dst':true,'time':'-6','desc':'SE Texas: Bryan, College Station, Bay City (split from 409, see also 936)'},
	'980':{'Region':'NC','dst':true,'time':'-5','desc':'North Carolina: (overlay on 704; perm 5/1/00, mand 3/15/01)'},
	'984':{'Region':'NC','dst':true,'time':'-5','desc':'E North Carolina: Raleigh (overlaid on 919, perm 8/1/01, mand 2/5/02 POSTPONED)'},
	'985':{'Region':'LA','dst':true,'time':'-6','desc':'E Louisiana: SE/N shore of Lake Pontchartrain: Hammond, Slidell, Covington, Amite, Kentwood, area SW of New Orleans, Houma, Thibodaux, Morgan City (split from 504; perm 2/12/01; mand 10/22/01)'},
	'989':{'Region':'MI','dst':true,'time':'-5','desc':'Upper central Michigan: Mt Pleasant, Saginaw (split from 517; perm 4/7/01)'},
	'999':{'Region':null,'dst':null,'time':null,'desc':'Often used by carriers to indicate that the area code information is unavailable for CNID, even though the rest of the number is present'}
};

/******************************************************************************
 * http://ajas.us/
 * The way I want to do socials (Apr 11, 2007) rev. (Jun 14, 2007)
 * If you find an error, or improvement, let me know: dev at ajas dot us
 * This file is free for use
 * 
 * ajas.ui.magicSocial()           - call this onblur, scrubs and validates
 * ajas.validator.social.validate()- call for true/false validation
 * ajas.validator.social.parse()   - don't call this
 *
 * The function cancelBubble() is found in ajas.event.js
 *
 * highgroup codes found monthly at http://www.ssa.gov/employer/highgroup.txt
 * SSA details: http://www.ssa.gov/employer/ssnweb.htm
 *****************************************************************************/

if(!ajas.validator.social)ajas.validator.social={};

// use this function for form feedback
ajas.ui.magicSocial=function(oInput, bStrict, bDashed) {
	// Set preferred defaults here.
	// bStrict=true will filter currently known area & group codes
	// bStrict=false will filter 000 area & group codes
	if(arguments.length < 2) var bStrict=true;
	// bDashed=true will add dashes
	// bDashed=false will return nine digits without dashes
	if(arguments.length < 3) var bDashed=true;
	
	var sLabel = oInput.id + 'Msg';
	oInput.className = oInput.className.replace(/ajas_social_error/g, '');
	try {
		if (oInput.value == '') {
			ajas._e(sLabel).innerHTML = '###-##-####';
			return;
		}
		var sSocial = ajas.validator.social.parse(oInput.value, bStrict);
		// Human readable SSN
		ajas._e(sLabel).innerHTML = sSocial.substr(0,3)+'-'+sSocial.substr(3,2)+'-'+sSocial.substr(5,4);
		oInput.value=bDashed?sSocial.substr(0,3)+'-'+sSocial.substr(3,2)+'-'+sSocial.substr(5,4):sSocial;
		return true;
	} catch (e) {
		oInput.className += ' ajas_social_error';
		var message = e.message;
		// Fix for IE6 bug
		if (message.indexOf('is null or not an object') > -1) {
			message = 'Invalid SSN';
		}
		try{ajas._e(sLabel).innerHTML=message;}catch(E){}
		return false;
	}

};

//use this function to get a quiet true/false response
ajas.validator.social.validate=function(sSocial) {
	try {
		ajas.validator.social.parse(sSocial, true);
	} catch (e) {
		return false;
	}
	return true;
};

ajas.validator.social.parse=function(sSocial, bStrict) {
	if(arguments.length < 2) var bStrict=true;

	if (aBits = /[a-z]/i.exec(sSocial)) {
		throw new Error("Invalid SSN");
	}

	sSocial = sSocial.replace(/[^0-9]/ig, '');

	if (sSocial.length != 9) {
		throw new Error("Invalid SSN length");
	}
	if (sSocial.substr(0,3) == '000') {
		throw new Error("Invalid SSN Area");
	}
	if (sSocial.substr(3,2) == '00') {
		throw new Error("Invalid SSN Group");
	}
	if (sSocial.substr(5,4) == '0000') {
		throw new Error("Invalid SSN Serial");
	}
	if (bStrict) {
		//check for unallocated areas (leading 3 digits)
		if (!ajas.validator.social.aHighGroups[sSocial.substr(0,3)]) {
			throw new Error("Invalid SSN Area");
		}
		//check for unallocated groups (middle 2 digits)
		var iHG=ajas.validator.social.aHighGroups[sSocial.substr(0,3)];
		var iSG=sSocial.substr(3,2);
		for(var i=0;i<ajas.validator.social.aGroupOrder.length;i++) {
			if (ajas.validator.social.aGroupOrder[i]==iSG) break;
			if (ajas.validator.social.aGroupOrder[i]==iHG) {
				throw new Error("Invalid SSN Group");
			}
		}
		
		//check for known invalid numbers/partials
		for(var i in ajas.validator.social.aAdvertSSN) {
			if (sSocial==ajas.validator.social.aAdvertSSN[i]) {
				throw new Error("Known Bad SSN");
			}
		}
		for(var i in ajas.validator.social.aBadCombo) {
			if (sSocial.substr(0,5)==ajas.validator.social.aBadCombo[i]) {
				throw new Error("Invalid SSN Area/Group");
			}
		}
	}

	return sSocial;
};

// "Within each area, the group number (middle two (2) digits) 
//  range from 01 to 99 but are not assigned in consecutive 
//  order. For administrative reasons, group numbers issued 
//  first consist of the ODD numbers from 01 through 09 and 
//  then EVEN numbers from 10 through 98, within each area 
//  number allocated to a State. After all numbers in group 98 
//  of a particular area have been issued, the EVEN Groups 02 
//  through 08 are used, followed by ODD Groups 11 through 99."
//  ODD - 01, 03, 05, 07, 09 
//  EVEN - 10 to 98
//  EVEN - 02, 04, 06, 08 
//  ODD - 11 to 99
ajas.validator.social.aGroupOrder= [
	'01','03','05','07','09','10','12','14','16','18','20','22',
	'24','26','28','30','32','34','36','38','40','42','44','46',
	'48','50','52','54','56','58','60','62','64','66','68','70',
	'72','74','76','78','80','82','84','86','88','90','92','94',
	'96','98','02','04','06','08','11','13','15','17','19','21',
	'23','25','27','29','31','33','35','37','39','41','43','45',
	'47','49','51','53','55','57','59','61','63','65','67','69',
	'71','73','75','77','79','81','83','85','87','89','91','93',
	'95','97','99'];
ajas.validator.social.aAdvertSSN = [
	'042103580','062360749','078051120','095073645','128036045',
	'135016629','141186941','165167999','165187999','165207999','165227999',
	'165247999','189092294','212097694','212099999','306302348','308125070',
	'468288779','549241889','987654320','987654321','987654322','987654323',
	'987654324','987654325','987654326','987654327','987654328','987654329'];
ajas.validator.social.aBadCombo = [
	'55019','58619','58629','58659','58679','58680','58681',
	'58682','58683','58684','58685','58686','58687','58688','58689','58690',
	'58691','58692','58693','58694','58695','58696','58697','58698','58699'];
	
//This function is useful for converting `highgroup.txt` to a JS object
ajas.validator.social.highGroupParse=function(s) {
	s="ajas.validator.social.aHighGroups={\n\t'"+s
		.substring(s.indexOf('001'))
		.replace(/\t/g,'  ')
		.replace(/\*/g,' ')
		.replace(/   /g,'  ')
		.replace(/ $/g,'').replace(/ $/g,'')
		.replace(/(\d) (\d)/g,"$1':'$2")
		.replace(/\r\n/g,'|').replace(/\r/g,'|').replace(/\n/g,'|')
		.replace(/\s+\|/g,'|')
		.replace(/\s+/g,"','")
		.replace(/\|+/g,"',\n\t'")
	;
	return s.substr(0,s.length-4)+"};";
};
ajas.validator.social.aHighGroups={
	'001':'06','002':'04','003':'04','004':'08','005':'08','006':'06',
	'007':'06','008':'90','009':'90','010':'90','011':'90','012':'90',
	'013':'90','014':'90','015':'90','016':'90','017':'90','018':'90',
	'019':'90','020':'90','021':'90','022':'90','023':'90','024':'90',
	'025':'88','026':'88','027':'88','028':'88','029':'88','030':'88',
	'031':'88','032':'88','033':'88','034':'88','035':'72','036':'72',
	'037':'72','038':'70','039':'70','040':'11','041':'11','042':'11',
	'043':'11','044':'11','045':'11','046':'11','047':'08','048':'08',
	'049':'08','050':'96','051':'96','052':'96','053':'96','054':'96',
	'055':'96','056':'96','057':'96','058':'96','059':'96','060':'96',
	'061':'96','062':'96','063':'96','064':'96','065':'96','066':'96',
	'067':'96','068':'96','069':'96','070':'96','071':'96','072':'96',
	'073':'96','074':'96','075':'96','076':'96','077':'96','078':'96',
	'079':'96','080':'96','081':'96','082':'96','083':'96','084':'96',
	'085':'96','086':'96','087':'96','088':'96','089':'96','090':'96',
	'091':'96','092':'96','093':'96','094':'96','095':'96','096':'96',
	'097':'96','098':'96','099':'96','100':'96','101':'96','102':'96',
	'103':'94','104':'94','105':'94','106':'94','107':'94','108':'94',
	'109':'94','110':'94','111':'94','112':'94','113':'94','114':'94',
	'115':'94','116':'94','117':'94','118':'94','119':'94','120':'94',
	'121':'94','122':'94','123':'94','124':'94','125':'94','126':'94',
	'127':'94','128':'94','129':'94','130':'94','131':'94','132':'94',
	'133':'94','134':'94','135':'19','136':'19','137':'19','138':'19',
	'139':'17','140':'17','141':'17','142':'17','143':'17','144':'17',
	'145':'17','146':'17','147':'17','148':'17','149':'17','150':'17',
	'151':'17','152':'17','153':'17','154':'17','155':'17','156':'17',
	'157':'17','158':'17','159':'84','160':'84','161':'84','162':'84',
	'163':'84','164':'84','165':'84','166':'84','167':'84','168':'84',
	'169':'84','170':'84','171':'84','172':'84','173':'84','174':'84',
	'175':'82','176':'82','177':'82','178':'82','179':'82','180':'82',
	'181':'82','182':'82','183':'82','184':'82','185':'82','186':'82',
	'187':'82','188':'82','189':'82','190':'82','191':'82','192':'82',
	'193':'82','194':'82','195':'82','196':'82','197':'82','198':'82',
	'199':'82','200':'82','201':'82','202':'82','203':'82','204':'82',
	'205':'82','206':'82','207':'82','208':'82','209':'82','210':'82',
	'211':'82','212':'79','213':'79','214':'77','215':'77','216':'77',
	'217':'77','218':'77','219':'77','220':'77','221':'06','222':'04',
	'223':'99','224':'99','225':'99','226':'99','227':'99','228':'99',
	'229':'99','230':'99','231':'99','232':'53','233':'53','234':'53',
	'235':'53','236':'51','237':'99','238':'99','239':'99','240':'99',
	'241':'99','242':'99','243':'99','244':'99','245':'99','246':'99',
	'247':'99','248':'99','249':'99','250':'99','251':'99','252':'99',
	'253':'99','254':'99','255':'99','256':'99','257':'99','258':'99',
	'259':'99','260':'99','261':'99','262':'99','263':'99','264':'99',
	'265':'99','266':'99','267':'99','268':'13','269':'13','270':'13',
	'271':'13','272':'13','273':'13','274':'13','275':'13','276':'13',
	'277':'13','278':'13','279':'13','280':'13','281':'11','282':'11',
	'283':'11','284':'11','285':'11','286':'11','287':'11','288':'11',
	'289':'11','290':'11','291':'11','292':'11','293':'11','294':'11',
	'295':'11','296':'11','297':'11','298':'11','299':'11','300':'11',
	'301':'11','302':'11','303':'31','304':'31','305':'31','306':'31',
	'307':'31','308':'31','309':'31','310':'31','311':'31','312':'31',
	'313':'31','314':'31','315':'31','316':'29','317':'29','318':'06',
	'319':'06','320':'06','321':'06','322':'06','323':'06','324':'06',
	'325':'06','326':'06','327':'06','328':'06','329':'06','330':'06',
	'331':'06','332':'06','333':'06','334':'06','335':'06','336':'06',
	'337':'06','338':'06','339':'06','340':'06','341':'06','342':'06',
	'343':'04','344':'04','345':'04','346':'04','347':'04','348':'04',
	'349':'04','350':'04','351':'04','352':'04','353':'04','354':'04',
	'355':'04','356':'04','357':'04','358':'04','359':'04','360':'04',
	'361':'04','362':'33','363':'33','364':'33','365':'33','366':'33',
	'367':'33','368':'33','369':'33','370':'33','371':'33','372':'33',
	'373':'33','374':'33','375':'33','376':'33','377':'33','378':'33',
	'379':'33','380':'33','381':'33','382':'33','383':'33','384':'33',
	'385':'33','386':'31','387':'29','388':'29','389':'29','390':'29',
	'391':'27','392':'27','393':'27','394':'27','395':'27','396':'27',
	'397':'27','398':'27','399':'27','400':'67','401':'67','402':'67',
	'403':'67','404':'67','405':'65','406':'65','407':'65','408':'99',
	'409':'99','410':'99','411':'99','412':'99','413':'99','414':'99',
	'415':'99','416':'61','417':'61','418':'61','419':'61','420':'61',
	'421':'61','422':'61','423':'59','424':'59','425':'99','426':'99',
	'427':'99','428':'99','429':'99','430':'99','431':'99','432':'99',
	'433':'99','434':'99','435':'99','436':'99','437':'99','438':'99',
	'439':'99','440':'23','441':'23','442':'23','443':'23','444':'23',
	'445':'21','446':'21','447':'21','448':'21','449':'99','450':'99',
	'451':'99','452':'99','453':'99','454':'99','455':'99','456':'99',
	'457':'99','458':'99','459':'99','460':'99','461':'99','462':'99',
	'463':'99','464':'99','465':'99','466':'99','467':'99','468':'49',
	'469':'49','470':'49','471':'49','472':'49','473':'49','474':'49',
	'475':'49','476':'49','477':'49','478':'37','479':'37','480':'37',
	'481':'37','482':'37','483':'37','484':'35','485':'35','486':'25',
	'487':'25','488':'25','489':'25','490':'25','491':'25','492':'25',
	'493':'25','494':'23','495':'23','496':'23','497':'23','498':'23',
	'499':'23','500':'23','501':'33','502':'31','503':'39','504':'39',
	'505':'51','506':'51','507':'51','508':'51','509':'27','510':'27',
	'511':'27','512':'27','513':'27','514':'25','515':'25','516':'43',
	'517':'43','518':'77','519':'75','520':'53','521':'99','522':'99',
	'523':'99','524':'99','525':'99','526':'99','527':'99','528':'99',
	'529':'99','530':'99','531':'61','532':'61','533':'61','534':'61',
	'535':'61','536':'61','537':'61','538':'59','539':'59','540':'73',
	'541':'73','542':'73','543':'71','544':'71','545':'99','546':'99',
	'547':'99','548':'99','549':'99','550':'99','551':'99','552':'99',
	'553':'99','554':'99','555':'99','556':'99','557':'99','558':'99',
	'559':'99','560':'99','561':'99','562':'99','563':'99','564':'99',
	'565':'99','566':'99','567':'99','568':'99','569':'99','570':'99',
	'571':'99','572':'99','573':'99','574':'49','575':'99','576':'99',
	'577':'45','578':'43','579':'43','580':'37','581':'99','582':'99',
	'583':'99','584':'99','585':'99','586':'61','587':'99','588':'01',
	'589':'99','590':'99','591':'99','592':'99','593':'99','594':'99',
	'595':'99','596':'84','597':'84','598':'82','599':'82','600':'99',
	'601':'99','602':'63','603':'63','604':'63','605':'63','606':'63',
	'607':'63','608':'63','609':'63','610':'63','611':'63','612':'63',
	'613':'63','614':'63','615':'63','616':'63','617':'63','618':'61',
	'619':'61','620':'61','621':'61','622':'61','623':'61','624':'61',
	'625':'61','626':'61','627':'08','628':'08','629':'08','630':'08',
	'631':'08','632':'08','633':'08','634':'08','635':'08','636':'08',
	'637':'08','638':'08','639':'08','640':'08','641':'08','642':'06',
	'643':'06','644':'06','645':'06','646':'94','647':'92','648':'44',
	'649':'42','650':'44','651':'44','652':'44','653':'42','654':'26',
	'655':'26','656':'24','657':'24','658':'24','659':'14','660':'14',
	'661':'14','662':'14','663':'14','664':'14','665':'14','667':'34',
	'668':'34','669':'34','670':'32','671':'32','672':'32','673':'32',
	'674':'32','675':'32','676':'12','677':'12','678':'12','679':'12',
	'680':'86','681':'12','682':'12','683':'12','684':'12','685':'12',
	'686':'12','687':'12','688':'10','689':'10','690':'10','691':'07',
	'692':'07','693':'07','694':'07','695':'07','696':'05','697':'05',
	'698':'05','699':'05','700':'18','701':'18','702':'18','703':'18',
	'704':'18','705':'18','706':'18','707':'18','708':'18','709':'18',
	'710':'18','711':'18','712':'18','713':'18','714':'18','715':'18',
	'716':'18','717':'18','718':'18','719':'18','720':'18','721':'18',
	'722':'18','723':'18','724':'28','725':'18','726':'18','727':'10',
	'728':'14','729':'10','730':'09','731':'09','732':'09','733':'09',
	'750':'09','751':'07','752':'01','753':'01','756':'05','757':'05',
	'758':'03','759':'03','760':'03','761':'03','762':'03','763':'03',
	'764':'78','765':'76','766':'60','767':'60','768':'60','769':'60',
	'770':'60','771':'60','772':'60'};
/******************************************************************************
 * http://ajas.us/
 * The way I want to do dates (Dec 7, 2006) rev. (May 14, 2007)
 * If you find an error, or improvement, let me know: dev at ajas dot us
 * This file is free for use
 *
 * I have not tested every date, but it seems to work for all the dates tested
 *
 * ajas.ui.magicDate()               - call this onblur, scrubs and validates
 * ajas.util.arraySearch()           - return first index of item in array
 * ajas.util.arrayFilter()           - filters array based on passed test function
 * ajas.validator.date.parseMonth()  - returns which single monthname a string represents
 * ajas.validator.date.parseWeekday()- returns which single dayname a string represents
 * ajas.validator.date.parse()       - don't call this
 * ajas.ui.magicDate_keyup()         - call this on keyup, handles special keys
 * ajas.validator.date.aMonthNames
 * ajas.validator.date.aWeekdayNames
 *
 * The function cancelBubble() is found in ajas.event.js
 *
 * based on:
 *  'Magic' date parsing, by Simon Willison (6th October 2003)
 *  http://simon.incutio.com/archive/2003/10/06/betterDateInput
 *****************************************************************************/

if(!ajas.validator.time)ajas.validator.time={};

ajas.validator.date.aMonthNames = ["January","February","March","April","May","June","July","August","September","October","November","December"];
ajas.validator.date.aWeekdayNames = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];


// use this function for form feedback
ajas.ui.magicTime=function(oInput, b24) {
	// Set preferred defaults here.
	// b24 sets whether to use 24 hour clock
	if(arguments.length < 2) var b24=false;
	
	var sLabel = oInput.id + 'Msg';
	oInput.className = oInput.className.replace(/ajas_time_error/g, '');
	try {
		if (oInput.value == '') {
			ajas._e(sLabel).innerHTML = b24?'HH:mm:ss':'hh:mm:ss am/pm';
			return;
		}
		var oDate = ajas.validator.time.parse(oInput.value,b24);
		var iHour=oDate.getHours();
		if(!b24)iHour%=12;
		if(!b24 && iHour == 0)iHour=12;
		var s='';
		if(iHour<10)s+='0';
		s+=iHour+':';
		if(oDate.getMinutes()<10)s+='0';
		s+=oDate.getMinutes()+':';
		if(oDate.getSeconds()<10)s+='0';
		s+=oDate.getSeconds();
		if(!b24)s+=oDate.getHours()>11?'pm':'am';
		
		oInput.value = s;
		// Human readable time
		ajas._e(sLabel).innerHTML = s;
	} catch (e) {
		oInput.className += ' ajas_time_error';
		var message = e.message;
		// Fix for IE6 bug
		if (message.indexOf('is null or not an object') > -1) {
			message = 'Invalid time string';
		}
		ajas._e(sLabel).innerHTML = message;
	}

};

ajas.validator.time.parse=function(sDate, b24) {
	if(arguments.length < 2) var b24=false;
	var aBits;
	var oDate = new Date();
	
	// Now
	if (aBits = /^now/i.exec(sDate)) {
		return oDate;
	}
	
	// Noon
	if (aBits = /^(12(00)?n|noon?|midd(ay?)?)/i.exec(sDate)) {
		oDate.setSeconds(0);
		oDate.setMinutes(0);
		oDate.setHours(12);
		return oDate;
	}
	// Midnight
	if (aBits = new RegExp('^'+sDate,'i').test('midnight')) {
		oDate.setSeconds(0);
		oDate.setMinutes(0);
		oDate.setHours(0);
		return oDate;
	}

	// 0530
	if (aBits = /^(\d{1,2})(\d{2})?(\d{2})?\s*(am?|pm?)?$/i.exec(sDate)) {
		var iHour=parseInt(aBits[1], 10);
		if (iHour > 23 || (iHour > 12 && !b24)){
			throw new Error("Invalid Hour");
		}
		var iMinute=parseInt(aBits[2], 10);
		if (iMinute > 59){
			throw new Error("Invalid Minute");
		}
		var iSecond=parseInt(aBits[3], 10);
		if (iSecond > 59){
			throw new Error("Invalid Second");
		}
		var iPM=0;
		if(aBits[4]){
			var iPM=aBits[4].toLowerCase().indexOf('p')>-1?12:0;
			if(iHour > 12){
				throw new Error("AM/PM on a 24-Hour clock?");
			}
			if(iHour == 12){
				iHour=0;
			}
		}
		if(!b24)iHour%=12;
		oDate.setSeconds(iSecond||0);
		oDate.setMinutes(iMinute||0);
		oDate.setHours(iHour+iPM);
		return oDate;
	}


	// hh:mm:ss
	if (aBits = /^(\d{1,2}):(\d{1,2})(:(\d{1,2}))?\s*(am?|pm?)?$/i.exec(sDate)) {
		var iHour=parseInt(aBits[1], 10);
		if (iHour > 23 || (iHour > 12 && !b24)){
			throw new Error("Invalid Hour");
		}
		var iMinute=parseInt(aBits[2], 10);
		if (iMinute > 59){
			throw new Error("Invalid Minute");
		}
		var iSecond=parseInt(aBits[4], 10);
		if (iSecond > 59){
			throw new Error("Invalid Second");
		}
		var iPM=0;
		if(aBits[5]){
			var iPM=aBits[5].toLowerCase().indexOf('p')>-1?12:0;
			if(iHour > 12){
				throw new Error("AM/PM on a 24-Hour clock?");
			}
			if(iHour == 12){
				iHour=0;
			}
		}
		if(!b24)iHour%=12;
		oDate.setSeconds(iSecond||0);
		oDate.setMinutes(iMinute||0);
		oDate.setHours(iHour+iPM);
		return oDate;
	}

	// now that I've checked what and how I want to check, let the JS API try what's left.
	if (milli = Date.parse(sDate)) { // is there a better way to do this?
		oDate.setSeconds(0);
		oDate.setMinutes(0);
		oDate.setHours(0);
		oDate.setDate(1);
		oDate.setFullYear(1970);
		oDate.setMonth(0); // Because months indexed from 0
		oDate.setMilliseconds(milli);
		return oDate;
	}
	
	// no date
	throw new Error("Enter a Valid Time.");
};

ajas.ui.magicTime_keyup=function(event,b24) {
	if(arguments.length < 2) var b24=false;

	event=event||window.event;
	var oInput=event.target||event.srcElement;
	if (false && "" == oInput.value){
		var oDate = new Date();
		oInput.value = oDate.getFullYear() + '-' + (oDate.getMonth() + 1) + '-' + oDate.getDate();
	} else {
		key = event.keyCode?event.keyCode:event.which;
		
		switch(key) {
		case 13: // enter
			oInput.blur();
			ajas.event.cancelBubble(event);
			return false;
			break;
		case 38: // up
			if (oInput.value == '') oInput.value='now';
			var oDate = ajas.validator.time.parse(oInput.value,b24);
			oDate.setHours(oDate.getHours() + 1);
			break;
		case 40: // down
			if (oInput.value == '') oInput.value='now';
			var oDate = ajas.validator.time.parse(oInput.value,b24);
			oDate.setHours(oDate.getHours() - 1);
			break;
		case 39: // right
			if (oInput.value != '') break;
			var oDate = new Date();
			break;
		default:
			return true;
		}
		if (oDate){
			var iHour=oDate.getHours();
			if(!b24)iHour%=12;
			if(!b24 && iHour == 0)iHour=12;
			var s='';
			if(iHour<10)s+='0';
			s+=iHour+':';
			if(oDate.getMinutes()<10)s+='0';
			s+=oDate.getMinutes()+':';
			if(oDate.getSeconds()<10)s+='0';
			s+=oDate.getSeconds();
			if(!b24)s+=oDate.getHours()>11?'pm':'am';
			
			oInput.value = s;
			// Human readable time
			ajas._e(oInput.id+'Msg').innerHTML = s;
		}
	}
	return true;
};

// use this function for form feedback
ajas.ui.magicCaps=function(event) {
	event=event||window.event;
	var oInput=event.target||event.srcElement;
	var sLabel = oInput.id + 'Msg';
	oInput.className = oInput.className.replace(/ajas_caps_error/g, '');
	if (ajas.event.testCapsLock(event)) {
		ajas._e(sLabel).innerHTML='Is Caps Lock on?';
		oInput.className += ' ajas_caps_error';
	} else {
		ajas._e(sLabel).innerHTML='&nbsp;';
	}
};


/******************************************************************************
 * The way I want to do stuff (2005) rev. (Apr 20, 2007)
 * If you find an error, or improvement, let me know: dev at ajas dot us
 * This file is free for use
 *
 * functions:
 *  String.prototype.trim()      - remove whitespace from both ends of Str
 *  parseQueryString(String)     - querystring to associative array
 *  arrayDump(Array[,String])    - [recusive] array to pre-text
 *  cancelBubble(Event)          - stop event propagation
 *  mouseCoords(Event)           - return coords of mouse event
 *  getFormValues(Form)          - returns form values as querystring
 *  arrayToTable(Array[, String])- [recusive] array to key-value table
 *****************************************************************************/

/******************************************************************************
 * http://ajas.us/
 * The way I want to do trees (May 08, 2007) rev. (May 14, 2007)
 * Looking and working right require proper CSS styling
 * If you find an error, or improvement, let me know: dev at ajas dot net
 * This file is free for use
 *
 * The function ajas.event.cancelBubble() is found in ajas.event.js
 *****************************************************************************/


ajas.ui.Tree=function(sDelim,fClick) {
	var oNewTree = document.createElement('ul');
	oNewTree.className='ajas_ui_tree';
	oNewTree.bRoot=true;
	oNewTree.oActiveNode=null;
	oNewTree.sDelim=(arguments.length < 1)?'/':sDelim;
	oNewTree.fNodeClick=(arguments.length < 2)?false:fClick;
	oNewTree.oDragNode=null;
	oNewTree.bEnableDrag=false;

	TreeNode = function(sName,aData) {
		//if(sName=='')return false;
		var oNewNode = document.createElement('li');

		//assign properties we don't mind overwritten
		oNewNode.sExpander='http://ajas.us/images/expander.gif';
		oNewNode.sCollapser='http://ajas.us/images/collapser.gif';
		oNewNode.sFolderOpen='http://ajas.us/images/folder_open.gif';
		oNewNode.sFolderClosed='http://ajas.us/images/folder_closed.gif';
		oNewNode.sLabel=sName;
		oNewNode.title=sName;
		oNewNode.className='ajas_ui_tree_node';
		
		//copy properties from passed object
		for(var i in aData) {
			oNewNode[i]=aData[i];
		}

		//assign properties we DO mind overwritten
		oNewNode.id='';
		oNewNode.sName=sName;
		oNewNode.bNode=true;
		
		oNewNode.oToggle=document.createElement('img');
		oNewNode.oToggle.className='ajas_ui_tree_toggle';
		oNewNode.oToggle.src='http://ajas.us/images/collapser.gif';
		oNewNode.appendChild(oNewNode.oToggle);
		
		oNewNode.oIcon=document.createElement('img');
		oNewNode.oIcon.className='ajas_ui_tree_nodeIcon';
		oNewNode.oIcon.src='http://ajas.us/images/folder_open.gif';
		oNewNode.appendChild(oNewNode.oIcon);
		
		oNewNode.oLabel=document.createElement('a');
		oNewNode.oLabel.className='ajas_ui_tree_nodeLabel';
		oNewNode.oLabel.innerHTML=oNewNode.sLabel;
		oNewNode.oLabel.href=oNewNode.sHREF||oNewNode.sLabel;
		oNewNode.appendChild(oNewNode.oLabel);
		
		oNewNode.oSubTree = document.createElement('ul');
		oNewNode.appendChild(oNewNode.oSubTree);

		oNewNode.setData = function(aData) {
			//copy properties from passed object
			for(var i in aData) {
				this[i]=aData[i];
			}
	
			//assign properties we DO mind overwritten
			this.id='';
			this.sName=sName;
			this.bNode=true;
		}
		oNewNode.getPath = function() {
			o=this;
			s='';
			while (o && !o.bRoot && o.tagName!='body' && o!=document.body) {
				if (o.bNode==true) {
					s='/'+o.sName+s;
				}
				o=o.parentNode;
			}
			return s;
		}
		
		oNewNode.toggle = function(bOpen) {
			if(arguments.length < 1) {
				bOpen=/_collapse$/.test(this.className);
			}
			if (bOpen) {
				this.className=this.className.replace(/_collapse$/,'');
				this.oToggle.src=this.sCollapser;
				this.oIcon.src=this.sFolderOpen;
			} else {
				this.className=this.className.replace(/_collapse$/,'')+'_collapse';
				this.oToggle.src=this.sExpander;
				this.oIcon.src=this.sFolderClosed;
			}
		}
		
		return oNewNode;
	};
	oNewTree.addNode = function(sPath, aData) {
		sPath=sPath.replace(new RegExp('^[^\\'+this.sDelim+']*\\'+this.sDelim+'+|\\'+this.sDelim+'+$','g'),'');
		if (sPath=='')return false;
		var aPath=sPath.split(this.sDelim);
		var oParentUL=this;
		
		//make sure the parent branches are ready
		for(var i=0;i<aPath.length-1;i++) {
			if(aPath[i]=='')continue;
			var oNode=false;
			for(var j in oParentUL.childNodes) {
				if (oParentUL.childNodes[j].sName == aPath[i]) {
					oNode=oParentUL.childNodes[j];
					break;
				}
			}
			if (!oNode) {
				oNode=new TreeNode(aPath[i],[i]);
				oParentUL.appendChild(oNode);
			}
			oParentUL=oNode.oSubTree;
		}
		
		//find specified node
		var oNode=false;
		for(var j in oParentUL.childNodes) {
			if (oParentUL.childNodes[j].sName == aPath[aPath.length-1]) {
				oNode=oParentUL.childNodes[j];
				break;
			}
		}
		if (!oNode) {
			var oNode=new TreeNode(aPath[aPath.length-1],aData);
			oParentUL.appendChild(oNode);
			return oNode; //node created
		}
		oNode.setData(aData);
		return false; //node not created, already existed
	};
	oNewTree.getNode = function(sPath) {
		sPath=sPath.replace(new RegExp('^[^\\'+this.sDelim+']*\\'+this.sDelim+'+|\\'+this.sDelim+'+$','g'),'');
		var aPath=sPath.split(this.sDelim);
		var oParentUL=this;
		for(var i=0;i<aPath.length;i++) {
			var oNode=false;
			for(var j in oParentUL.childNodes) {
				if (oParentUL.childNodes[j].sName == aPath[i]) {
					oNode=oParentUL.childNodes[j];
					break;
				}
			}
			if (!oNode) {
				return false; //not found
			}
			oParentUL=oNode.oSubTree;
		}
		return oNode; //false if not found, object if found
	};
	oNewTree.showNode = function(oNode) {
		var o=oNode;
		while (o && !o.bRoot) {
			if (o.bNode)o.toggle(true);
			o=o.parentNode;
		}
		o.scrollIntoView(false);
	};
	oNewTree.deleteNode = function(oNode) {
		oNode.parentNode.removeChild(oNode);
		oNode=null;
	};
	oNewTree.moveNode = function (oNode, oDest) {
		if (!oNode.bNode || !oDest.bNode) return false;
		if (oNode==oDest) return false;
		var o=oDest;
		while (o && !o.bRoot) {
			o=o.parentNode;
			if(oNode==o) return false;
		}
		oNode.parentNode.removeChild(oNode);
		oDest.oSubTree.appendChild(oNode);
	};
	oNewTree.setActiveNode = function(oNode,bShow) {
		if (this.oActiveNode) {
			this.oActiveNode.oLabel.id='';
		}
		if (oNode) {
			this.oActiveNode=oNode;
			this.oActiveNode.oLabel.id='ajas_ui_tree_activeNode';
			if (bShow) this.showNode(oNode);
		}
	};
	oNewTree.onselectstart = function(event) {
		ajas.event.cancelBubble(event||window.event);
	};
	oNewTree.onclick = function(event) {
		event=event||window.event;
		ajas.event.cancelBubble(event);
		var o=event.target||event.srcElement;
		if (o.className=='ajas_ui_tree_toggle') {
			o.parentNode.toggle();
		}
		if (o.className=='ajas_ui_tree_nodeIcon' || o.className=='ajas_ui_tree_nodeLabel') {
			this.setActiveNode(o.parentNode);
			if (this.fNodeClick) this.fNodeClick(this.oActiveNode);
		}
	};
	oNewTree.onmousedown = function (event) {
		event=event||window.event;
		ajas.event.cancelBubble(event);
		if (!this.bEnableDrag)return false;
		var o=event.target||event.srcElement;
		if(o.className!='ajas_ui_tree_nodeIcon' && o.className!='ajas_ui_tree_nodeLabel')return;
		this.oDragNode=o.parentNode;
	};
	oNewTree.onmouseup = function (event) {
		event=event||window.event;
		ajas.event.cancelBubble(event);
		if (!this.bEnableDrag)return false;
		var o=event.target||event.srcElement;
		while (o && !o.bNode) {
			o=o.parentNode;
		}
		if (this.oDragNode) {
			this.moveNode(this.oDragNode,o);
			this.oDragNode.oLabel.style.top=0;
			this.oDragNode.oLabel.style.left=0;
			this.oDragNode.oLabel.style.position='';
			this.oDragNode.oLabel.className='ajas_ui_tree_nodeLabel';
		}
		this.oDragNode=null;
	};
	oNewTree.onmousemove = function (event) {
		event=event||window.event;
		ajas.event.cancelBubble(event);
		if (!this.bEnableDrag)return false;
	
		if (this.oDragNode) {
			var mc=ajas.event.mouseCoords(event);
			this.oDragNode.oLabel.style.position='absolute';
			this.oDragNode.oLabel.style.top=(mc.y );
			this.oDragNode.oLabel.style.left=(mc.x+12);
			this.oDragNode.oLabel.className='ajas_ui_tree_spanDrag';
		}
	};
	return oNewTree;
};



ajas.util.tableSort=function(tbody_id, col, t){
	if (t != "str" && t != "date" && t != "year/month" && t != "num" && t != "last") return;
	if(!ajas.util.tableSort.sortCol)ajas.util.tableSort.sortCol=-1;
	if(!ajas.util.tableSort.sortDir)ajas.util.tableSort.sortDir=1;
	if (ajas.util.tableSort.sortCol == col) {
		ajas.util.tableSort.sortDir *= -1;
	}
	ajas.util.tableSort.sortCol=col;
	ajas.util.tableSort.sortType=t;
	rows=[];
	tbody=ajas._e(tbody_id);
	len=tbody.rows.length;
	for(i=0;i<len;i++){
		rows[i]=tbody.rows[i];
	}
	rows.sort(ajas.util.tableSort.compareRow);
	
	while(tbody.firstChild){
		tbody.removeChild(tbody.firstChild);
	}
	for (i=0;i<rows.length;i++) {
		tbody.appendChild(rows[i]);
	}
};

ajas.util.compare=function(a,b) {
	return (a<b?-1:(a>b?1:0));
};
ajas.util.tableSort.compareRow=function(a,b) {
	var col=ajas.util.tableSort.sortCol;
	if ("input"==ajas.util.tableSort.sortType) {
		return ts_stringComparator(a.cells[col].childNodes[0].value, 
			b.cells[col].childNodes[0].value);
	}
	valA=a.cells[col].innerHTML;
	valB=b.cells[col].innerHTML;
	
	if (ajas.util.tableSort.sortType == "str") {
		return ajas.util.compare(valA.toLowerCase(), valB.toLowerCase());
	} else if (ajas.util.tableSort.sortType == "date") {
		return ajas.util.compare(new Date(valA), new Date(valB));
	} else if (ajas.util.tableSort.sortType == "year/month") {
		var re=/([\d]{4}), ([\w]+)/;
		return ajas.util.compare(new Date(valA.replace(re,"$2 1, $1")),
			new Date(valB.replace(re,"$2 1, $1")));
	} else if (ajas.util.tableSort.sortType == "num") {
		re=/\$|%|,/g;
		return ajas.util.compare(new Number(valA.replace(re,"")),
			new Number(valB.replace(re,"")));
	} else if (ajas.util.tableSort.sortType == "last") {
		return ajas.util.compare(valA.split(' ').pop().toLowerCase(),
			valB.split(' ').pop().toLowerCase());
	} else {
		return 0;
	}
};

ajas.util.addLoadHandler=function(f){
	var o=window.onload;
	if(typeof window.onload!='function'){
		window.onload=f;
	}else{
		window.onload=function(){if(o)o();f();}
	}
}

