	/* Javascript Utility Functions
	   
	   Change Log:
	   		03/04/08    FL      Fixed addEventToObj did work in IE
	   		03/03/08    FL      Added functions addClass and removeClass
	   		10/09/07	AK		Added some DOM creation utilities
	   		10/06/07	AK		Added GetEvent(), GetEventTarget() and HandleFormKey()
	   		09/24/07	AK		Added GetCurrentTimeMS()
	   		09/14/07	AK		Fixed LocateLoginDiv
	  		09/14/07	AK		Added ChildHasClassName
	   		09/07/07	AK		Added LoadXMLDoc
	   		08/15/07	AK		Added Cookie functions
	   		07/29/07	AK		Added ShowHideElementInline() for elements that need to be 
	   							inline and not block
	   		06/28/07	AK		Created
	   		
	   To Do:
	   		@todo Clean up Event Mgmt utilities
	   		@todo Clean up DOM Element utilities
	   		@todo Use DOM creation utilities in all JS widgets
	*/


var preFilledFields = new Array();  // Associative array to hold fields that are prepopultated and their original text, key is ID of the field.
var skipFields = new Array();  // Array to hold fields tha are to be ignored.
var checkFields = true;  // Do we perform the check?  Add Experience and cancel buttons set this to false.
var editorID = "";       // ID of the FCKEditor if any on the page sowe can check if it's dirty or not.

	//----------------------------------------------------------------------
	// 	LocateLoginDiv		Sets the location for the login window
	//----------------------------------------------------------------------
	function LocateLoginDiv() {
		var loginElem = document.getElementById('loginDiv');
		var signinElem = document.getElementById('newUser');
		
		if ((loginElem != null) && (signinElem != null)) {
			var p = GetElementPosition(signinElem);
			loginElem.style.left = (p.x + signinElem.offsetWidth - loginElem.offsetWidth) + "px";
			loginElem.style.top = (p.y + signinElem.offsetHeight) + "px";
		}
	} // LocateLoginDiv
	
	
	//----------------------------------------------------------------------
	// 	GetCookie		Returns the value of the requested var from the
	//					current cookie
	//		key			- variable name
	//		return		- value as string
	//----------------------------------------------------------------------
	function GetCookie(key) {
		var payload = document.cookie;
		if (payload == "")
			return null;
		var start = payload.indexOf(key + "=");
		if (start == -1)
			return null;
		var len = start + key.length + 1;		
		var end = payload.indexOf(';',len);
		if (end == -1)
			end = payload.length;
			
		return unescape(payload.substring(len,end));
	} // GetCookie()


	//----------------------------------------------------------------------
	// 	SetCookie		Sets the cookie key/value pair with the given
	//					cookie attributes
	//		key			- variable to set
	//		value		- value to set
	//		expires		- expiration in days
	//		path		- cookie path
	//		domain		- site domain name
	//		secure		- ??
	//----------------------------------------------------------------------
	function SetCookie(key,value,expires,path,domain,secure) {
		var today = new Date();
		today.setTime(today.getTime());
		if (expires)
			expires = expires * 1000 * 60 * 60 * 24;							// days => ms
		var expires_date = new Date(today.getTime() + expires);
		if (value != "") value = escape(value);
		
		document.cookie = key + '=' + value +
			((expires) ? ';expires='+expires_date.toGMTString() : '') +
			((path) ? ';path=' + path : '') +
			((domain) ? ';domain=' + domain : '') +
			((secure) ? ';secure' : '');
	} // SetCookie()


	//----------------------------------------------------------------------
	// 	DeleteCookie	Deletes the given variable from the cookie by 
	//					setting the value to nothing and setting the 
	//					expiration to the past
	//		key			- variable to set
	//		path		- cookie path
	//		domain		- site domain name
	//----------------------------------------------------------------------
	function DeleteCookie(key,path,domain) {
		//if (GetCookie(key)) {

			document.cookie = key + '=' + 
				((path) ? ';path=' + path : '') +
				((domain) ? ';domain=' + domain : '') +
				';expires=Thu, 01-Jan-1970 00:00:01 GMT';
		//} else {
		//	alert("can't find cookie - " + key);
		//}
	} // DeleteCookie()
	
	
	//----------------------------------------------------------------------
	// 	SplitOnce		Splits the string into two pieces based on the 
	//					first occurrence of the delimiter; we do this because
	//					JS string.split() will split all occurrences and 
	//					limit the return array (which is different)
	//		str			- haystack
	//		delim		- delimiter string
	//		reurn		: array of strings with 2 elements
	//----------------------------------------------------------------------
	function SplitOnce(srcStr,delim) {
		var splitList = new Array(2);
		
		var delimLoc = srcStr.indexOf(delim);		// find first occurrence
		if (delimLoc == -1)
			return null;						// empty array
		
		splitList[0] = srcStr.substr(0,delimLoc);
		splitList[1] = srcStr.substr(delimLoc + delim.length);
		return splitList;
	} // SplitOnce()
		

	//----------------------------------------------------------------------
	// 	LoadXMLDoc		Issues an AJAX call with a call back for the 
	//					xml response
	//		url			- call to make
	//		isGET		- GET or POST
	//		call_back	- routine to execute when complete
	//		cmd			- caller defined parameter
	//		async		- true or false
	//----------------------------------------------------------------------	
	function LoadXMLDoc(url,isGET,call_back,callBackParam,async) {
		var ajaxRequest = null;
		
		// determine browser support
		if (window.XMLHttpRequest)
			ajaxRequest = new XMLHttpRequest();							// mozilla
		else if (window.ActiveXObject)
			ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");		// ie
			
		if (ajaxRequest == null) {
			LogErrorToServer("browser does not support ajax",0,0);
			return;
		}
		
		//alert("LoadXMLDoc: ("+async+")" + url);
		//debugger;
		
		// set up the call back if it's an async call
		if (async)
			ajaxRequest.onreadystatechange = function() { if (call_back) call_back(ajaxRequest,callBackParam); };
		
		if (isGET) {
			ajaxRequest.open("GET",url,async);
			ajaxRequest.setRequestHeader("Content-Type","text/xml");
			ajaxRequest.send(null);
			if ((! async) && (call_back))
				call_back(ajaxRequest,callBackParam);	// we call it ourselves on sync calls
		}
		else {
			// if POST, we pull the vars off the URL string & send separately
			var postStr = "";
			var strList = SplitOnce(url,"?");
			if (strList != null) {
				url = strList[0];
				postStr = strList[1];
			}
			
			ajaxRequest.open("POST",url,async);
			// this will tell PHP to stuff the variables into the $_POST field
			ajaxRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
			ajaxRequest.send(postStr);
			if ((! async) && (call_back))
				call_back(ajaxRequest,callBackParam);	// we call it ourselves on sync calls
		}
	} // LoadXMLDoc()
	
	/*
	 * Returns the current time in millisecs
	 * 
	 * return (int) - time in ms
	 */
	function GetCurrentTimeMS() {
		// FF lets us use Date.now() but for some reason Safari chokes on it
		var d = new Date();
		var t = d.getTime();
		delete d;		// so we don't leak memory
		return t;
	} // GetCurrentTimeMS()
	
	
	/*
	 * Logs the given JS error to the PHP server log using ajax
	 * 
	 * @param message - the text error message from JS
	 * @param url - the url it came from
	 * @param line - the line number on the page
	 */
	function LogErrorToServer(message,url,line) {
		var browser = navigator.userAgent;
		var paramList = encodeURI("?msg=" + message + "&url=" + url + "&line=" + line + "&info=" + browser);
		// POST, no callback, sync or IE won't stop logging the bug
		LoadXMLDoc("log_jserror.php" + paramList,false,null,0,false);		
		return true; 														// we handled, so don't pass through to browser
	} // LogErrorToServer()
	
	
	function warnOnLimitText(limitField, warningdiv, limitNum) {
        if (warningdiv) {
			var str = limitField.value;
            if (str.length >= limitNum) {
    			warningdiv.style.display = "block";
    			limitField.value = str.substring(0, limitNum);
    			warningdiv.innerHTML = _VARS.warnOnLimitText.max + limitNum + _VARS.warnOnLimitText.chars;
    			return false;
    		} else {
    		    warningdiv.style.display = "none";
    		}
        }
        return true;
    }
	
	/*
	 * Handles the enter/return key on a form
	 * 
	 * @param Object e - event object
	 * @return Boolean - false if we're handling the event
	 */
	function HandleFormKey(e) {
		e = GetEvent(e);
		// if (e.type != "keypress") return true;
		// if (e.type != "keyup") return true;
		
		var code = String.fromCharCode(e.charCode ? e.charCode : e.keyCode);
		var elem = GetEventTarget(e);
		
		// traverse up the tree to find the FORM element
		do {
			if (elem.nodeName.toLowerCase() != 'form') continue;
				
			if (code == "\r") {
				elem.submit();
				return false;
			}
		} while (elem = elem.parentNode);
		
		return true;
	} // HandleFormKey()
	
	/**
	 * Handles a keystroke to see if it's legal based on the rules provided
	 * in the rt_allowed attribute; if not legal, the element id named in 
	 * attribute rt_message is made visible
	 * @param Object e - Event object
	 * @return Boolean - false if illegal & we need to eat the char
	 */
	function HandleFieldKey(e) {
		// get the event object, target element, char code and character
		e = GetEvent(e);
		var elem = GetEventTarget(e);
		var code = GetEventCharCode(e);
		var ch = GetEventCharFromCode(e);
		var tolong = false;
		
		// get msg elem to show when test fails
		var msgID = elem.getAttribute("rt_message");
		var msgElem = (msgID) ? DOMGetElem(msgID) : null;
		
		var msgText = elem.getAttribute("rt_text");
		
		var msgLength = elem.getAttribute("rt_length");
		
		if (msgLength) {
		    if (elem.value.length >= msgLength) {
    		    //msgText = "You can only type a maximum of " + msgLength + " characters";
		    msgText = _VARS.HandleFieldKey.max + msgLength + _VARS.HandleFieldKey.chars;
    		    tolong = true;
    		}
		}
		
		// don't filter control keys (FF sets charCode to 0 for function keys)
		var keySafe = ((e.charCode == 0) || (e.ctrlKey || e.altKey) || (code < 32));
		
		// get the regex ruleset; don't filter if it's empty
		var reStr = elem.getAttribute("rt_allowed");
		keySafe = (keySafe || reStr == "");
		
		// test the regex rule
		var legalChars = new RegExp(reStr);
		keySafe = (keySafe || legalChars.test(ch));
		
		if (tolong) {
		    keySafe = false;
		}
		
		// show/hide the fail msg
		if (msgElem) {
			msgElem.style.visibility = (keySafe) ? "hidden" : "visible";
			if (msgText && msgText.length > 0) {
			   msgElem.innerHTML = msgText;	
			}
		}
	
		if (keySafe) return true;
		
		// eat event
		if (e.preventDefault) e.preventDefault( );
		if (e.returnValue) e.returnValue = false;
		return false;
	 } // Event_KeyFilter()
	
	/* Cross browser Event Manager */
	function GetEvent(e) { return e || window.event; }
	function GetEventTarget(e) { return e.target || e.srcElement; }
	function GetEventCharCode(e) { return e.charCode || e.keyCode; }
	function GetEventCharFromCode(e) { 
		var c = String.fromCharCode(GetEventCharCode(e));
		return (e.shiftKey) ? c : c.toLowerCase();
	}
	
	/*
	 * Adds an event listener in a cross browser fashion
	 * 
	 * @param Object obj       - dom element to add the lsiter to
	 * @param String evType    - event to listen for without the "on" prefix
	 * @param String fn        - function name to call
	 * @return Boolean - false if we're handling the event
	 *
	 * this same code lives in ibox.js but dind't want to call that from every page.
	 */
	function addEventToObj( obj, type, fn ){
		if (obj.addEventListener)
			obj.addEventListener( type, fn, false );
		else if (obj.attachEvent)
		{
			obj["e"+type+fn] = fn;
			obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
			obj.attachEvent( "on"+type, obj[type+fn] );
		}
	}

	/*
 	 * remove any events form on object that are nolonger need. 
 	 * can be used in conjuction with addEventToObj
	 * @param Object obj       - dom element to add the lsiter to
	 * @param String evType    - event to listen for without the "on" prefix
	 * @param String fn        - function name to call
	 * @return Boolean - false if we're handling the event
	 */
	function removeEventFrmObj( obj, type, fn ) {
		if (obj.removeEventListener)
			obj.removeEventListener( type, fn, false );
		else if (obj.detachEvent)
		{
			obj.detachEvent( "on"+type, obj[type+fn] );
			obj[type+fn] = null;
			obj["e"+type+fn] = null;
		}
	}

	/*
	 * Cancels the feild check on a page - used when the user has hit cancel or submit.
	 * we only check on back button or window close.
	 * 
	 */
	function cancelCheckFields() {
		checkFields = false;
		window.onbeforeunload = null;
	}
	
	/*
	 * Checks if a given element in in the global skiplist array
	 *
	 * @param Element	elem	 - element to find
	 *                       
	 * @return Boolean          - TRUE if the element is in the array, FALSE otherwise
	 */
	function IsFieldOnSkipList(elem) {
		if ((skipFields == null ) || (skipFields.length < 1)){
			return false;
		}
		for (var i = 0; i < skipFields.length; i++) {
			if (elem.id == skipFields[i]) {
				return true;
			}
		}
		return false;
	}
	
	/*
	 * Checks if a given element in in the global prefilled array
	 *
	 * @param Element	elem	 - element to find
	 *                       
	 * @return Boolean          - TRUE if the element is in the array with the same value, FALSE otherwise
	 */
	function IsFieldOnPreFilledList(elem) {
		if (preFilledFields == null ) {
			return false;
		}
		if (elem.type == "text") {
			if (preFilledFields[elem.id] == elem.value) {
		   		return true;
			}
		}
		return false;
	}
	
	/*
	 * Adds a check onunload to a page to see if any of the fields have data in them
	 *
	 * @param Array  arr	 - Associative array containing field ID's and values in those fields that can be ignored
	 *                         used for fields that have existing text like "enter your description here".
	 * @param Array  skiparr - Array for field IDs that can be ignored.
	 * @aram  String text    - ID of an FCKEditor instance on the page, if any.
	 * 
	 * @return void          - No return just hooks up the event listener
	 */
	function addCheckPageForValues(pagename,arr,skiparr,text, callBack) {
		editorID = text;
		preFilledFields = arr;
		skipFields = skiparr;

		window.onbeforeunload = function() {
			var isDirty = false;

			if (checkFields == true) {
				var main = document.getElementById('main');
				var elements = document.getElementsByTagName('input');

				// loops through all input tags on the page
				for (var i = 0; i < elements.length; i++) {
					var element = elements[i];
					// if they are textfields or radio buttons we care
					if ((element.type == "text") || (element.type == "radio")) {
						// make sure that they are on the main part of the page, not the headers or footers
						if (IsChild(element,main)) {
							if (IsFieldOnSkipList(element)) {
								continue;
							}
							if (IsFieldOnPreFilledList(element)) {
								continue;
							}
							if (element.type == "radio") {
								if (element.checked == true) {									
									isDirty = true;
								}	

							}
							else if (element.value.length > 0) {								
								isDirty = true;
							}
						} 
					}

				}
				var oEditor = FCKeditorAPI.GetInstance(editorID) ;
				if (oEditor != null) {
					if (oEditor.IsDirty() && (oEditor.GetHTML().length > 0)) {
						isDirty = true;
					}
				}
				
				if (!isDirty && callBack)
					isDirty = callBack();
				
				if (isDirty) {
					saveBackupData(pagename);
					var message = _VARS.addCheckPageForValues.message;
					if (typeof evt == 'undefined') {
					    evt = window.event;
					}
					if (evt) {
					    evt.returnValue = message;
					}
					return message;
				}
				else {
					removeBackUpData(pagename);
				}
			}
			else {
				// ie work around - ie fires the onbeforeunload event for all links on a page.
				checkFields = true;
				return;
			}
		};
	}
	
	function removeBackUpData(pagename) {
	    if (document.all) {
		   document.all["cachetag"].removeAttribute(pagename);
		   document.all["cachetag"].save("cache");	
	    }
	    else {	 
		  DeleteCookie(pagename,null,null);
		}
	}
	
	function checkBackupData(pagename) {
		var str = null;
		if (document.all) {
			document.all["cachetag"].load("cache");
			str = document.all["cachetag"].getAttribute(pagename);
		}
		else {
	   	   str = GetCookie(pagename);
	    }
		
		if (str && str.length > 0) {
			return true;
		}
		else {
			return false;
		}	
	}
	
	
	function escapeHTML(str)
	{
	   var div = document.createElement('div');
	   var text = document.createTextNode(str);
	   div.appendChild(text);
	   return div.innerHTML;
	};
	
	function restoreBackupData(pagename) {
		var str = null;
		if (document.all) {
	        document.all["cachetag"].load("cache");
		    str = document.all["cachetag"].getAttribute(pagename);
		}
		else {
		    str = GetCookie(pagename);
		}
		
		if (str && str.length > 0) {
	       var arr = str.split("|~|");
		
			 for (var i = 0; i < arr.length; i++ ) {
			 	 if (arr[i].length > 0) {
	    		     var subArr = arr[i].split("||");
	              if (subArr[0] == "e") {	                    
		               var oEditor = FCKeditorAPI.GetInstance(subArr[1]) ;
							if (oEditor != null) {
						       if (subArr[2] && subArr[2].length > 0) {
							      oEditor.SetData(subArr[2]);
							   }
							}
							else {
								alert(_VARS.restoreBackupData.alert_editor);
							}
						
	              }
					  else {
					      var elem = document.getElementById(subArr[1])						
						   if  (subArr[0] == "r") {
							    if (elem) {
								     elem.checked = subArr[2];
								 }
						   }
						   if (subArr[0] == "t") {
						       if (elem) {
							        elem.value = UnJSSafe(subArr[2]);
						  	    }
						   }
					  }     
	           }
			}

		}
		else {
			alert(_VARS.restoreBackupData.alert_cookie);
		}
	}
	
	function backupData(pagename,arr,skiparr,text) {
		editorID = text;
		preFilledFields = arr;
		skipFields = skiparr;
		saveBackupData(pagename);
	}	
		
	function saveBackupData(pagename) {
		var main = document.getElementById('main');
		var elements = document.getElementsByTagName('input');
		
		var str = "";

		// loops through all input tags on the page
		for (var i = 0; i < elements.length; i++) {
			var element = elements[i];
			// if they are textfields or radio buttons we care
			if ((element.type == "text") || (element.type == "radio")) {
				// make sure that they are on the main part of the page, not the headers or footers
				if (IsChild(element,main)) {
					if (IsFieldOnSkipList(element)) {
						continue;
					}
					if (IsFieldOnPreFilledList(element)) {
						continue;
					}
					if (element.type == "radio") {
						if (element.checked == true) {
							str = str + "|~|r||" + element.id  + "||checked";
						}	

					}
					else if (element.value.length > 0) {
						str = str + "|~|t||" + element.id  + "||" + element.value;
					}
				} 
			}

		}
		var oEditor = FCKeditorAPI.GetInstance(editorID) ;
		if (oEditor != null) {
			if (oEditor.IsDirty()) {
				var tmp = oEditor.GetHTML();
				if (tmp.length < 8000)
					str = str + "|~|e||" + editorID  + "||" + oEditor.GetHTML();
			}
		}
		if (document.all) {
		   document.all["cachetag"].setAttribute(pagename,str);
		   document.all["cachetag"].save("cache");	
		} 
		else {
		    SetCookie(pagename,str,1,null,null,null);
		}

		setTimeout('saveBackupData(\"' + pagename + '\")',10000);
	}
	
	
	function checkSavedReaction() {
		var str = null;
		if (document.all) {
			document.all["cachetag"].load("cache");
			str = document.all["cachetag"].getAttribute("savereaction");
		}
		else {
	   	   str = GetCookie("savereaction");
	    }
		
		if (str && str.length > 0) {
			return true;
		}
		else {
			return false;
		}	
	}
	
	function saveReaction(title,tags,text) {
		var str = "";
		str = "|~|t||title||" + title + "|~|t||tags||" + tags + "|~|e||text||" +  text;
		
		if (document.all) {
		   document.all["cachetag"].setAttribute("savereaction",str);
		   document.all["cachetag"].save("cache");	
		} 
		else {
		    SetCookie("savereaction",str,1,null,null,null);
		}
	}
	

	
	
/* parseUri 1.2; MIT License
By Steven Levithan <http://stevenlevithan.com> */

var parseUri = function (source) {
	var o = parseUri.options,
	value = o.parser[o.strictMode ? "strict" : "loose"].exec(source);
	
	for (var i = 0, uri = {}; i < 14; i++) {
		uri[o.key[i]] = value[i] || "";
	}
	
	uri[o.q.name] = {};
	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
		if ($1) uri[o.q.name][$1] = $2;
	});
	
	return uri;
};

parseUri.options = {
	strictMode: false,
	key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
	q: {
		name: "queryKey",
		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
	},
	parser: {
		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
		loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
	}
};


/**
 * JavaScript printf/sprintf functions.
 *
 * This code is unrestricted: you are free to use it however you like.
 * 
 * The functions should work as expected, performing left or right alignment,
 * truncating strings, outputting numbers with a required precision etc.
 *
 * For complex cases, these functions follow the Perl implementations of
 * (s)printf, allowing arguments to be passed out-of-order, and to set the
 * precision or length of the output based on arguments instead of fixed
 * numbers.
 *
 * See http://perldoc.perl.org/functions/sprintf.html for more information.
 *
 * Implemented:
 * - zero and space-padding
 * - right and left-alignment,
 * - base X prefix (binary, octal and hex)
 * - positive number prefix
 * - (minimum) width
 * - precision / truncation / maximum width
 * - out of order arguments
 *
 * Not implemented (yet):
 * - vector flag
 * - size (bytes, words, long-words etc.)
 * 
 * Will not implement:
 * - %n or %p (no pass-by-reference in JavaScript)
 *
 * @version 2007.04.27
 * @author Ash Searle
 */
function sprintf() {
	
	function pad(str, len, chr, leftJustify) {
		var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
		return leftJustify ? str + padding : padding + str;
	} // pad()

	function justify(value, prefix, leftJustify, minWidth, zeroPad) {
		var diff = minWidth - value.length;
		if (diff > 0) {
			if (leftJustify || !zeroPad)
				value = pad(value, minWidth, ' ', leftJustify);
			else
			value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
		}
		return value;
	} // justify()

	function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
		// Note: casts negative numbers to positive ones
		var number = value >>> 0;
		prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
		value = prefix + pad(number.toString(base), precision || 0, '0', false);
		return justify(value, prefix, leftJustify, minWidth, zeroPad);
	} // formatBaseX()

    function formatString(value, leftJustify, minWidth, precision, zeroPad) {
		if (precision != null)
			value = value.slice(0, precision);
		return justify(value, '', leftJustify, minWidth, zeroPad);
	} // formatString()

    var a = arguments,
    	i = 0,
    	format = a[i++];
    	
    return format.replace(sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
	    if (substring == '%%') return '%';

	    // parse flags
	    var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false;
	    for (var j = 0; flags && j < flags.length; j++)
		    switch (flags.charAt(j)) {
				case ' ': positivePrefix = ' '; break;
				case '+': positivePrefix = '+'; break;
				case '-': leftJustify = true; break;
				case '0': zeroPad = true; break;
				case '#': prefixBaseX = true; break;
		    }

	    // parameters may be null, undefined, empty-string or real valued
	    // we want to ignore null, undefined and empty-string values
	    if (!minWidth)
			minWidth = 0;
	    else if (minWidth == '*')
			minWidth = +a[i++];
	    else if (minWidth.charAt(0) == '*')
			minWidth = +a[minWidth.slice(1, -1)];
	    else
			minWidth = +minWidth;

	    // Note: undocumented perl feature:
	    if (minWidth < 0) {
			minWidth = -minWidth;
			leftJustify = true;
	    }

	    if (!isFinite(minWidth))
			throw new Error('sprintf: (minimum-)width must be finite');

	    if (!precision)
			precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
	    else if (precision == '*')
			precision = +a[i++];
	    else if (precision.charAt(0) == '*')
			precision = +a[precision.slice(1, -1)];
	    else
			precision = +precision;

	    // grab value using valueIndex if required?
	    var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

	    switch (type) {
			case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad);
			case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
			case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
			case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'i':
			case 'd': {
				      var number = parseInt(+value);
				      var prefix = number < 0 ? '-' : positivePrefix;
				      value = prefix + pad(String(Math.abs(number)), precision, '0', false);
				      return justify(value, prefix, leftJustify, minWidth, zeroPad);
				  }
			case 'e':
			case 'E':
			case 'f':
			case 'F':
			case 'g':
			case 'G':
			          {
				      var number = +value;
				      var prefix = number < 0 ? '-' : positivePrefix;
				      var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
				      var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
				      value = prefix + Math.abs(number)[method](precision);
				      return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
				  }
			default: return substring;
	    } // switch
	});
} // sprintf()
sprintf.regex = /%%|%(\d+\$)?([-+#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;

/**
 * Trival printf implementation, probably only useful during page-load.
 * Note: you may as well use "document.write(sprintf(....))" directly
 */
function printf() {
    // delegate the work to sprintf in an IE5 friendly manner:
    var i = 0, a = arguments, args = Array(arguments.length);
    while (i < args.length) args[i] = 'a[' + (i++) + ']';
    document.write(eval('sprintf(' + args + ')'));
}

/**
 * Takes an IMG dom element and applies the IE6 filter to it
 * @param Object elem - IMG element
 * @param Boolean doScale	- Whether to use scale or image methods. We differentiate because
 * 								scale seems unreliable in search results
 */
function FixIE6Transparency(elem,doScale) {


	if (! isIE6) return;
	if (elem == null) return;
	if (! /\.png$|\.png\?rcach*/.test(elem.src.toLowerCase())) return;		// not a png	
 	var curSrc = elem.src;
	elem.src = "img/spacer.gif";						// have to zap the src, dunno why
	var sizeMethod = (doScale) ? 'scale' : 'image';
	elem.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true," + 
															"sizingMethod='" + sizeMethod  + "', src='" + curSrc + "')";
} // FixIE6Transparency()

/**
 * sets the globals below for a couple of safari detections
 */
var isSafari = false;
var isSafari304 = false;
var isSafari2 = false;
var isIE6 = false;
var isIE7 = false;
var isFF = false;
var isLeopard = false;
function browserDetect() {
	var ua = navigator.userAgent.toLowerCase();
	var re = new RegExp('Version/(.+)Safari/', 'i');
	var matches = new Array();
	matches = re.exec(navigator.userAgent); //
	if (matches != null && matches[0].length > 0) {
		ver = matches[1]; // version
		isSafari = true;
		if (ver == '3.0.4') isSafari304 = true;
		else if (ver.indexOf('2.') == 0) {
			isSafari2 = true;
		}
		else {
			//alert(ver);
		}
	}
	else if (navigator.userAgent.indexOf('Safari') >= 0) {
		isSafari2 = true;
		//alert("safari2");
	}
	else if ((ua.indexOf('msie 6') != -1) && (ua.indexOf('opera') == -1)) {
		isIE6 = true;
	}
	else if (ua.indexOf('msie 7') != -1) {
		isIE7 = true;
	} 
	else if (ua.indexOf('firefox/') != -1) {
		isFF = true;
		// test for leopard - but not on non-macintosh machines :-)
		// this was for the flash issue, but that has been resolved under leopard
		// so commenting out to avoid launching a JVM
		//if (ua.indexOf('macintosh') != -1 && typeof(java) != 'undefined') {
		//	var ver = java.lang.System.getProperty('os.version');
		//	if (typeof(ver) != 'undefined' && ver.indexOf('10.5') >= 0)
		//		isLeopard = true;
		//}
	}
}

/**
 * Leopard + an early version of flash == bad. Return true if this os/flash version is
 * incompatible with flash file upload.
 */
function shouldHideFlashUpload() {
	browserDetect();
	if (isLeopard || isSafari304) {
		x = navigator.plugins["Shockwave Flash"];
		if (x)
		{
			if (x.description)
			{
				y = x.description;
				flashversion = y.charAt(y.indexOf('.')-1);
				flashrelease = y.substr(y.indexOf(' r')+2);
				if (flashversion <= 9 && flashrelease < 98)
					return true;
			}
		}
		//if (isSafari304 || isFF) return true;
	}
	
	return false;
}

function safeJavaScriptStringLiteral(str) {

  str = str.replace("\\","\\\\"); // escape single backslashes
  str = str.replace("'","\\'"); // escape single quotes
  str = str.replace("\"","\\\""); // escape double quotes
  //str = str.replace("<","\\<"); // escape open angle bracket
  //str = str.replace(">","\\>"); // escape close angle bracket
  return str;
}

/**
 * relace every non-alpha non-numeric char with _. Truncate
 * string at a backslash slash b/c php will.<b> 
 */
 function normalizeFilename(name) {
	var will_truncate = name.lastIndexOf("\\");
	if (will_truncate >= 0) {
		name = name.substring(will_truncate+1);
	}
	
	return name.replace(/[^A-Za-z0-9.]/g,"_");
}

function normalizeFilePartOfURL(url) {
	var offset = url.lastIndexOf("/");
	var path = url.substring(0,offset+1);
	var name = normalizeFilename(url.substring(offset+1));
	
	return path + name;
}


function validateValidEmail(email) {
	var invalidChars = '\/\'\\ ";:?!()[]\{\}^|';
	for (i = 0; i < invalidChars.length; i++) {
	   if (email.indexOf(invalidChars.charAt(i),0) > -1) {
	      return false;
	   }
	}

	for (i = 0; i < email.length; i++) {
	   if (email.charCodeAt(i) > 127) {
	      return false;
	   }
	}

	var atPos = email.indexOf('@',0);
	if (atPos == -1) {
	   return false;
	}
	if (atPos == 0) {
	   return false;
	}
	if (email.indexOf('@', atPos + 1) > - 1) {
	   return false;
	}
	if (email.indexOf('.', atPos) == -1) {
	   return false;
	}
	if (email.indexOf('@.',0) != -1) {
	   return false;
	}
	if (email.indexOf('.@',0) != -1){
	   return false;
	}
	if (email.indexOf('..',0) != -1) {
	   return false;
	}

	var domain = email.substring(email.lastIndexOf('.') + 1);
	domain = domain.toLowerCase();

	if (domain.length < 2 && domain != 'com' && domain != 'net' && domain != 'org' && 
    	domain != 'asia' && domain != 'arpa' && domain != 'biz' && domain != 'aero' && domain != 'name' && domain != 'tel' && 
	    domain != 'edu' && domain != 'int' && domain != 'mil' && domain != 'gov' && domain != 'jobs' && domain != 'travel' &&     
	    domain != 'coop' && domain != 'info' && domain != 'pro' &&  domain != 'museum' && domain != 'mobi') {
	    return false;
	}

	return true;
}


function validateZIP(zip) {
	var valid = "0123456789-";
	var hyphencount = 0;

	if (zip.length != 5 && zip.length != 10) {
		return false;
	}
	for (var i = 0; i < zip.length; i++) {
		temp = "" + zip.substring(i, i+1);
		if (temp == "-") hyphencount++;
		if (valid.indexOf(temp) == "-1") {
			return false;
		}
		if ((hyphencount > 1) || ((zip.length==10) && "" + zip.charAt(5) != "-")) {
			return false;
   		}
	}
	return true;
}

function validateEmail(email){
	var filter = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
	if (!email.match(filter)) {
		//alert('Email address: ' + email + ' must be valid.');
		alert(_VARS.validateEmail.email + email + _VARS.validateEmail.valid);
		return false;
	}
	return true;
}

/**
 * Returns true if given var is null or undefined
 */
function IsNull(aVar) {
	return((aVar === null) || (typeof(aVar) == 'undefined'));
}

function trim(str, chars) {
    return ltrim(rtrim(str, chars), chars);
}

function ltrim(str, chars) {
    chars = chars || "\\s";
    return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}

function rtrim(str, chars) {
    chars = chars || "\\s";
    return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

/**
* Reverses the JSSafe function on StringUtils.php
*/
function UnJSSafe(str) {
	var result = str;
	result = result.replace(/&amp;/gi,"&" );
	result = result.replace(/&lt;/gi,"<");
	result = result.replace(/&gt;/gi,">");
	result = result.replace(/&quot;/gi,"\"");
	result = result.replace(/&apos;/gi,"'");
	result = result.replace(/&#39;/gi,"'");
	result = result.replace(/&#039;/gi,"'");
	result = result.replace(/&#40;/gi,"(");
	result = result.replace(/&#040;/gi,"(");
	result = result.replace(/&#41;/gi,")");
	result = result.replace(/&#041;/gi,")");
	result = result.replace(/&#92;/gi,"\\");
	result = result.replace(/&#092;/gi,"\\");
	result = result.replace(/<br\/>/gi,"\n");
	
   return result;
}


function limitText(limitField, limitNum) {
    if (limitField.value.length > limitNum) {
        limitField.value = limitField.value.substring(0, limitNum);
    } 
}

/*
* Called when the user changes the selected image
* 
* @param (String) imgSrc - the image to display
*/
function ShowMainImage(imgSrc, video_player) {
	var MAX_SIZE = 250;
	var force_size = false; // for video thumbs, force the size b/c they are small
	var videoSrc = imgSrc; // make a copy, imgSrc will get munged if this is a video
	var showVideoPlayer = false;

	// if this is a video link, show a dialog (ibox) with the video player
	if (imgSrc.match(/youtube.com/)) {
		// convert it to a preview image if not already converted
		if (!imgSrc.match(/.jpg/)) {
			// convert to a preview URL with overlay
			var id = imgSrc.substring(imgSrc.lastIndexOf("/")+1);
			var tmpSrc = "http://img.youtube.com/vi/" + id + "/default.jpg";
			imgSrc = "overlay.php?src=" + tmpSrc;
		}
		else
			imgSrc = "overlay.php?src=" + imgSrc;
			
		force_size = true;
		showVideoPlayer = (video_player != "no_video_player");
	}
	
	// set the main photo to the new source
	var elem = document.getElementById('main_photo');
	if (elem)
		elem.src = imgSrc;
	
	var lelem = document.getElementById('main_photo_link');
	if (lelem) {
	    if (imgSrc.indexOf("youtube.com") <= 0) {
	    	if ((imgSrc.indexOf("story_images") <= 0) &&
				(imgSrc.indexOf("profile_images") <= 0) &&  
				(imgSrc.indexOf("hub_images") <= 0))
	        	lelem.href = imgSrc + ".orig";
	       else
				lelem.href = imgSrc;
        }
        else {
			var children = lelem.firstChild;
			var lparent = lelem.parentNode;
			$('#main_photo_link').attr('href',videoSrc + "&rel=0&hl=en&fs=1&autoplay=1");
			$('#main_photo_link').addClass('videoPS');

//			lparent.removeChild(elem);
//			lparent.appendChild(children);
        }
    }

	if (!showVideoPlayer && $('#main_photo_link').hasClass('videoPS'))
		$('#main_photo_link').removeClass('videoPS');

	// size the main photo but keep perspective ratio
	// We create a new image here because the existing main photo will have its
	// old size, not the size of the new image
	var srcElem = new Image();
	srcElem.src = imgSrc;
	var destWidth = srcElem.width;
	var destHeight = srcElem.height;
	
	// if the dimensions are bad the image is still loading so we call ourselves once the image
	// has completed loading and set it to the bad dimensions until then
	// if the dimonesions are fine, we check whether we have to scale it
	if (!force_size && ((destWidth == 0) || (destHeight == 0))) {
		srcElem.onload = function() {return(ShowMainImage(imgSrc));};
	}
	else if (force_size || (srcElem.width > MAX_SIZE) || (srcElem.height > MAX_SIZE)) {
		if (force_size) {
			// it starts 130x97 but the dimenstions are 0,0 at this point b/c it has not loaded.
			srcElem.width = MAX_SIZE;
			srcElem.height = Math.floor((MAX_SIZE/130) * 97);	// proportionally scale
		}
		
		if (srcElem.width >= srcElem.height) {
			destHeight = Math.floor(MAX_SIZE * srcElem.height / srcElem.width);
			destWidth = MAX_SIZE;
		}
		else {
			destWidth = Math.floor(MAX_SIZE * srcElem.width / srcElem.height);
			destHeight = MAX_SIZE;
		}
	}
	elem.width = destWidth;
	elem.height = destHeight;
//	if (showVideoPlayer) elem.onclick = function() { return ShowVideo(videoSrc); };
	
	FixIE6Transparency(elem,true);
	
	delete srcElem;
} // ShowMainImage()

	/*
	 * Adds a class to an object if it doesn't already have one,
	 * or appends a class to an existing class attribute.
	 */
	function addClassLoc(oElem, strClass) {
		if (!oElem.className) {
			oElem.className = strClass;
		}
		else {
			var newClassName = oElem.className;
			newClassName += " ";
			newClassName += strClass;
			oElem.className = newClassName;
		}
		return true;
	}

	/*
	 * removes the specified class from an object
	 */

	function removeClassLoc(oElem, strClass) {
		if (oElem.className) {
			var classArray = oElem.className.split(' ');
			var classUpper = strClass.toUpperCase();
			var iCount = classArray.length;
			for (i=0; i<iCount; i++) {
				if (classArray[i] && classArray[i].toUpperCase() == classUpper) {
					classArray.splice(i, 1);
					i--;
				}
			}
			oElem.className = classArray.join(' ');	
		}
		return false;
	}


function getPosition(obj) {
    var curleft = curtop = 0;
    if (obj.offsetParent) {
        do {
    		curleft += obj.offsetLeft;
    		curtop += obj.offsetTop;
        } while (obj = obj.offsetParent);
    }
    return [curleft,curtop];
        
}

function clientWidth() {
	return filterResults (
		window.innerWidth ? window.innerWidth : 0,
		document.documentElement ? document.documentElement.clientWidth : 0,
		document.body ? document.body.clientWidth : 0
	);
}
function clientHeight() {
	return filterResults (
		window.innerHeight ? window.innerHeight : 0,
		document.documentElement ? document.documentElement.clientHeight : 0,
		document.body ? document.body.clientHeight : 0
	);
}
function scrollLeft() {
	return filterResults (
		window.pageXOffset ? window.pageXOffset : 0,
		document.documentElement ? document.documentElement.scrollLeft : 0,
		document.body ? document.body.scrollLeft : 0
	);
}
function scrollTop() {
	return filterResults (
		window.pageYOffset ? window.pageYOffset : 0,
		document.documentElement ? document.documentElement.scrollTop : 0,
		document.body ? document.body.scrollTop : 0
	);
}
function filterResults(n_win, n_docel, n_body) {
	var n_result = n_win ? n_win : 0;
	if (n_docel && (!n_result || (n_result > n_docel)))
		n_result = n_docel;
	return n_body && (!n_result || (n_result > n_body)) ? n_body : n_result;
}
