function FL_ASSERT(boolExpressionResult, msg)
//  Throws a Debugger option when the boolean expression evals to false.  Only happens when the container is running as DEBUG.
//  example:	FL_ASSERT((myVar >= 0), 'myVar >= 0');
//  (The msg is necessary because we don't have access to the "text" of the code used for the first argument.)
//	If not running in debug, attempts to log the error silently.
{
	if (!boolExpressionResult)
	{
		if (typeof(msg) == 'undefined')
			msg = '[no message specified]';
		msg = fl_autoTruncateText(msg, 500, 15);  // limit to 500 chars or 15 line feeds
		popupMsg = 'JavaScript Assertion Warning\n\n' + msg + '\n\nChoose OK to generate an error for debugging.\nChoose Cancel to ignore the assertion and continue.\n\nWhen entering the debugger, go one level back in the call stack to see the real warning.';
		if (fl_isDebug())
		{
			if (window.confirm(popupMsg))
				debugger;
		}
		else
		{
			try
			{
				globalLogger.Warn(LOGCOMPONENT_OTHER, '#####  JavaScript Assertion Warning  #####\n'+msg);
			} catch(e) {}
		}
	}
	return boolExpressionResult;
}

function FL_ASSERT_PARAM_TYPE(paramRef, dataTypeAsString)
{
	if (typeof(paramRef) == 'undefined')
		FL_ASSERT(false, 'Function parameter missing');
	else if (typeof(paramRef) != dataTypeAsString)
		FL_ASSERT(false, 'Invalid data type for function parameter ['+String(arguments[0])+']; expected ['+dataTypeAsString+']');
}




//	Given a string that looks like a list with a consistent delimiter, 
//	adds an element to that list and returns the new list.
//	(If bPrepend is true, adds to the front of the list.)
//	ex.  fl_addToStringList('a_b_c_d_e', 'foo', '_')  returns  'a_b_c_d_e_foo'
//	Delimiter defaults to a comma (,).
//	The advantage of using this is that it makes sure the string isn't 
//	already in the list before adding it.
function fl_addToStringList(strList, strToAdd, /*optional*/strDelimiter, bPrepend)
{
	if (typeof(strList) != 'string')
	{
		FL_ASSERT(false, 'fl_addToStringList()... invalid parameter.');
		return;
	}

	if (typeof(strToAdd) == 'string'  &&  strList.indexOf(strToAdd) >= 0)  // don't bother if it's already there
		return strList;

	if (typeof(strDelimiter) == 'undefined')
		strDelimiter = ',';
	if (typeof(bPrepend) == 'undefined')
		bPrepend = false;

	//	work with an array even if only one string was passed
	var aList = ('' !== strList)  ?  strList.split(strDelimiter)  :  [];
	var aStringsToAdd = (typeof(strToAdd) == 'object'  ||  typeof(strToAdd) == 'array')  ?  strToAdd  :  [strToAdd];
	var addedItems = [];
	for (var i=0; i<aStringsToAdd.length; i++)
	{
		var strToAdd = aStringsToAdd[i];
		if (!fl_inArray(aList, strToAdd))
		{
			addedItems.push(strToAdd);
		}
	}
	if (bPrepend)
		return (addedItems.join(strDelimiter) + strDelimiter + aList.join(strDelimiter));
	else
		return (aList.join(strDelimiter) + strDelimiter + addedItems.join(strDelimiter));
}

//	Given a string that looks like a list with a consistent delimiter, 
//	removes an element from that list and returns the new list.
//		ex.  fl_removeFromStringList('a_b_c_d_e', 'c', '_')  returns  'a_b_d_e'
//	Delimiter defaults to a comma (,).
//	Wildcards also work:
//		fl_removeFromStringList('foo1,foo2,bar1,foo3,bar2', 'bar*')  returns  'foo1,foo2,foo3'
function fl_removeFromStringList(strList, strToRemove, /*optional*/strDelimiter)
{
	if (typeof(strList) != 'string')
	{
		FL_ASSERT(false, 'fl_removeFromStringList()... invalid parameter.');
		return;
	}

	if (typeof(strToAdd) == 'number')
		strToAdd = String(strToAdd);

	if (typeof(strDelimiter) == 'undefined')
		strDelimiter = ',';

	var aList = ('' !== strList)  ?  strList.split(strDelimiter)  :  [];
	var newArray = [];  // don't use "splice" here b/c we need to keep our place

	//	work with an array even if only one string was passed
	var aTmpStringsToRemove = (typeof(strToRemove) == 'object'  ||  typeof(strToRemove) == 'array')  ?  strToRemove  :  [strToRemove];
	var aStringsToRemove = [];
	for (var iRemove=0; iRemove<aTmpStringsToRemove.length; iRemove++)
	{
		var pair = [];
		//  if strToRemove ended in a wildcard, match any member that begins with the string up to the wildcard.
		if (aTmpStringsToRemove[iRemove].indexOf('*') == (aTmpStringsToRemove[iRemove].length-1))
		{
			pair.str = aTmpStringsToRemove[iRemove].substr(0, (aTmpStringsToRemove[iRemove].length-1));  // trim off asterisk
			pair.bWild = true;
		}
		else
		{
			pair.str = aTmpStringsToRemove[iRemove];
			pair.bWild = false;
		}
		aStringsToRemove.push(pair);
	}
	
	for (var iList=0; iList<aList.length; iList++)
	{
		var strToCheck = aList[iList];
		var bAnyMatch = false;
		for (var iRemove=0; iRemove<aStringsToRemove.length; iRemove++)
		{
			var bOneMatch = false;
			var strToRemove = aStringsToRemove[iRemove].str;
			var bWild = aStringsToRemove[iRemove].bWild;

			if (bWild)
				bOneMatch = (0 === strToCheck.indexOf(strToRemove))  // not found at beginning of string
			else
				bOneMatch = (strToCheck == strToRemove)  // direct comparison
			if (bOneMatch)
			{
				bAnyMatch = true;
				break;
			}
		}
		if (!bAnyMatch)
			newArray.push(strToCheck);
	}
	return newArray.join(strDelimiter);
}







//  used heavily in ServiceMgr.js
function fl_escapeXml(xml)
{
    //  If null or undefined, return empty string
	if (typeof(xml) == 'undefined'  ||  xml === null)  // rare but possible...would throw exception with regex below
		return('');

    //  If there are valid contents but it's not a string var, just return it back untouched since our substitutions only work on strings.
    if (typeof(xml) != 'string')
        return xml;

//	return encodeURIComponent(xml);  // too strong...tries to replace @, commas, etc.  All we really care about is PARSING...< > and & .
	xml = xml.replace(/&/g, '&amp;');  // always do ampersand first so it doesn't double-escape any others
	xml = xml.replace(/</g, '&lt;');
	xml = xml.replace(/>/g, '&gt;');
	xml = xml.replace(/\'/g, '&#39;');  //  apostrophe  (IE doesn't like &apos;)
	xml = xml.replace(/\"/g, '&quot;');
	return(xml);
}

function fl_unescapeXml(xml)
{
	if (typeof(xml) == 'undefined'  ||  xml === null)  // rare but possible...would throw exception with regex below
		return('');

	xml = xml.replace(/&lt;/g, '<');
	xml = xml.replace(/&gt;/g, '>');
	xml = xml.replace(/&#39;/g, '\'');  //  apostrophe
	xml = xml.replace(/&apos;/g, '\'');  //  apostrophe
	xml = xml.replace(/&quot;/g, '\"');
	xml = xml.replace(/&amp;/g, '&');
	return(xml);
}

function escapeSingleQuotes(str)
{
	return(str.replace(/\'/g, '\\\''));
}

function escapeDoubleQuotes(str)
{
	return(str.replace(/\"/g, '\\"'));
}


/*
Strips leading and trailing whitespace from sString
*/
function Trim(sString)
{
	sString = sString.replace(/^\s */, ''); // trim left
	sString = sString.replace(/\s *$/, ''); // trim right
	return sString;
}


/*
Strips all spaces from sString
*/
function TrimAll(sString)
{
	return sString.replace(/ /g, '');
}


function fl_autoTruncateText(text, maxNumChars, maxNumLines)
{
	if (text.length >= maxNumChars)
		text = text.substr(0,maxNumChars) + '...';

	if (typeof(maxNumLines) != 'undefined')
	{
		var testLF = text.split('\n');
		if (testLF.length > maxNumLines)
		{
			testLF.length = maxNumLines;
			text = testLF.join('\n') + '...';
		}
	}
	return text;
}




//	=========================   Arrays & Structures   =============================

function fl_isEnumeratedArray(a)
{
	if (typeof(a) != 'object'  ||  null === a)  // null is technically an "object"
		return false;

	if (typeof(a.length) != 'number')
		return false;

	if (a.length > 0)
		return true;

	//  At this point, if it's not empty then it must be associative.
	for (var i in a)
	{
		return false;  // simply getting here means "a" had at least one "i"
	}

	//	If empty, it could be considered either, but we'll call it enumerated.
	return true;
}

/*  bool */function fl_inArray(array, element)
/*  Returns true/false if something is in an ENUMERATED (simple) array.
	Checks value, not key -- 'b' would be true for ['a','b','c']
*/
{
	if (typeof(array) != 'object'  ||  null === array)
		return false;

	for (var z=0; z<array.length; z++)  // simple arrays
		if (array[z] == element)  return(true);
	return(false);
}

/* int */function fl_posInArray(array, element)
/*  Returns the position that something is found in an ENUMERATED (simple) array.  -1 if not found
	'b' would be 1 for ['a','b','c'].  'd' would yield -1.
*/
{
	if (typeof(array) != 'object'  ||  null === array)
		return false;

	for (var z=0; z<array.length; z++)  // simple arrays
		if (array[z] == element)  return(z);
	return(-1);
}

/*  bool */function fl_indexOf2dimArray(array, i2dimPosition, test)
/*  Returns true/false if something is in an ENUMERATED (simple) array.
	Checks value, not key -- 'b' would be true for ['a','b','c']
*/
{
	if (typeof(array) != 'object'  ||  null === array)
		return -1;

	for (var z=0; z<array.length; z++)
	{
		if (typeof(array[z][i2dimPosition]) != 'undefined'  &&  array[z][i2dimPosition] == test)
			return z;
	}
	return -1;
}






//	######################  Validation of input  #######################


/*
    fl_validateString(stringIn, CRITERIA, CRITERIA_2, ...);

        Returns true/false if stringIn meets all criteria in the given validation argument(s).  Can be used for
        long strings or single characters (useful for keystroke events.)

        Additional arguments after the string are one or more of the following:

            Common
                FL_VALIDATE_NONBLANK        //  must contain at least one non-space char.  Sufficient for most text input.
                FL_VALIDATE_NUMERIC         //  contains only chars 0-9
                FL_VALIDATE_FILEPATH        //  cannot contain \/:*?"<>| (typical windows restriction) and cannot be blank.

            Use these sparingly.
                FL_VALIDATE_ALPHANUMERIC    //  Only letters or numbers
                FL_VALIDATE_ALPHA           //  Only letters
                    These restrict everything else, including underscores, hyphens, etc.

        examples:
            fl_validateString('         ',   FL_VALIDATE_NONBLANK);   returns false b/c after trimming leading/trailing spaces, it's blank

            fl_validateString('12345A',      FL_VALIDATE_NUMERIC);    returns false b/c of 'A'

            fl_validateString('File Name',   FL_VALIDATE_FILEPATH);   returns true
            fl_validateString('File/Name',   FL_VALIDATE_FILEPATH);   returns false b/c of '/'
            fl_validateString('         ',   FL_VALIDATE_FILEPATH);   returns false b/c FILEPATH includes the NONBLANK check

            fl_validateString('hello-there', FL_VALIDATE_NONBLANK, FL_VALIDATE_ALPHANUMERIC);   returns false b/c of '-'


    validateKey(CRITERIA, CRITERIA_2, ...);

        Same behavior as validateString, except this is used in an keypress event handler.  We don't have to pass the string or event.
        example:
            <input id="areaCode" onkeypress="return validateKey(FL_VALIDATE_NUMERIC);" />
*/
function fl_validateString(stringIn/*,  ... */)  //  additional arguments are the above validation constants
{
    var ok = true;
    for (var i=1; i<arguments.length; i++)  // start with 2nd arg
    {
        ok = arguments[i](stringIn);
        if (!ok)
            break;  // stop testing at 1st false
    }
    return ok;
}

function fl_validateInputElement(element/*,  ... */)
{
    if (typeof(element) == 'string')
		element = document.getElementById(element);

    var ok = true;
    if (null !== element  &&  typeof(element.value) != 'undefined')
    {
		for (var i=1; i<arguments.length; i++)  // start with 2nd arg
		{
			ok = arguments[i](element.value);
			if (!ok)
				break;  // stop testing at 1st false
		}
	}
	else
	{
		alert('could not find element ['+String(element)+']');
	}
    return ok;
}



FL_NUMERIC_CHARS = '0123456789';
FL_NON_ALPHA_CHARS = '`-=~!@#$%^&*()_+[]\\{}|;\':",./<>?';
FL_FILEPATH_BADCHARS = '\\/:*?"<>|';
FL_DATABASE_BADCHARS = '%';

function FL_VALIDATE_DATABASE(str)
//	restricts chars that are known to cause problems in the database
{
    for (var i=0; i<str.length; i++)
    {
        if (FL_DATABASE_BADCHARS.indexOf(str.charAt(i)) >= 0)
            return false;
    }
    return true;
}

function FL_VALIDATE_NONBLANK(str)
{
    str = Trim(str);
    return(str.length > 0);
}

function FL_VALIDATE_NUMERIC(str)
{
	//	Returns true if str is either a real number, or a string containing only numeric characters.
	//	Multiplying by 1 and testing for NaN is much faster internally than using regex or stepping through the string.
	if (typeof(str) == 'undefined')
	{
		return false;
	}
	if(FL_VALIDATE_NONBLANK(str))  // do this first, so "whitespace * 1" doesn't equal 1 and give a false positive
		return(!isNaN(str*1));
	else
		return false;
}

function FL_VALIDATE_FILEPATH(str)
{
    if (!FL_VALIDATE_NONBLANK(str))
        return false;

    for (var i=0; i<str.length; i++)
    {
        if (FL_FILEPATH_BADCHARS.indexOf(str.charAt(i)) >= 0)
            return false;
    }
    return true;
}

function FL_VALIDATE_ALPHA(str)
//	Returns true if str is only made up of chars that are NOT known non-alpha chars, and NOT numbers 0-9.
//	We can't just test for A-Z, b/c that only works in English.
{
	if (typeof(str) != 'string')
	{
		return false;
	}
	else
	{
		return (null === str.match(/[0-9`\-=~!@#\$%\^\&\*\(\)_\+\[\]\\\{\}\|;':",\.\/<>\?]/g));
	}
}

function FL_VALIDATE_ALPHANUMERIC(str)
//	Returns true if str is only made up of chars that are NOT known non-alpha chars.
//	We can't just use 0-9/A-Z, b/c that only works in English.
{
	if (typeof(str) != 'string')
	{
		return false;
	}
	else
	{
		return (null === str.match(/[`\-=~!@#\$%\^\&\*\(\)_\+\[\]\\\{\}\|;':",\.\/<>\?]/g));
	}
}

function FL_VALIDATE_ID(str)
//	Returns true if str is only made up of chars that are valid for most "ID" values (in programming languages, HTML, etc.)
{
	if (typeof(str) != 'string')
	{
		return false;
	}
	else
	{
		return (null === str.match(/[^\w]/g));
	}
}

function FL_VALIDATE_EMAIL(str)
{
	if (typeof(str) != 'string')  return false;
	if ('' === str)  return false;

	var pieces = fl_getEmailPieces(str);
	return (null !== pieces);
}

function fl_getEmailPieces(str)
{
	var pieces = [];
	var atPieces = str.split('@');
	if (2 != atPieces.length)  // must be exactly one at-sign
		return null;

	pieces.username = atPieces[0];
	pieces.domain = String(atPieces[1]);

	var dotPieces = pieces.domain.split('.');
	if (dotPieces.length < 2)  // must be at least one dot
		return null;

	pieces.topLevelDomain = dotPieces[dotPieces.length-1];
	pieces.companyName = dotPieces[dotPieces.length-2];
	return pieces;
}

function isNumeric(str)
{
	return FL_VALIDATE_NUMERIC(str);
}

function fl_isHexString(hexString)
{
	var chars = 'afAF09';
	var a = chars.charCodeAt(0);
	var f = chars.charCodeAt(1);
	var A = chars.charCodeAt(2);
	var F = chars.charCodeAt(3);
	var zero = chars.charCodeAt(4);
	var nine = chars.charCodeAt(5);

	var nLength = hexString.length;
	for (var i=0;i<nLength;i++)
	{
		var tc = hexString.charCodeAt(i);
		if (!(a <= tc && tc <= f) &&
			!(A <= tc && tc <= F) &&
			!(zero <= tc && tc <= nine))
			return false;
	}
	return true;
}


function fl_createIdFromText(str)
//	Removes all non-ID-compatible characters from text.
{
	if (typeof(str) != 'undefined'  &&  '' != str)
		return str.replace(/[^\w]/g, '');
	else
		return '';
}

function fl_forceSSL()
{
	window.__bIsSSL = true;
}

function fl_isSSL()
{
	if (typeof(window.__bIsSSL) != 'undefined')  // sometimes a page will want to force this
		return window.__bIsSSL;
	else
		return ('https:' == window.location.protocol);
}

function fl_getCurrentProtocol()
{
	if (typeof(window.__bIsSSL) != 'undefined'  &&  window.__bIsSSL)  // sometimes a page will want to force this
		return 'https:';
	else
		return window.location.protocol;
}

function fl_getCurrentDomain()
{
	return window.location.hostname;
}

function fl_getProtocolFromUrl(url)
{
	if (typeof(url) != 'string')  throw { name:'ParameterError', message:'fl_getProtocolFromUrl(url) expected a string value.' };
	var protocol = '';
	var slices = url.split('://');
	if (slices.length > 1)
	{
		return (slices[0] + ':');  // JavaScript protocols are expected to include the colon but not the slashes
	}
	return protocol;
}

function fl_matchCurrentProtocol(url)
{
	var currentProt = fl_getCurrentProtocol();
	var urlProt = fl_getProtocolFromUrl(url);
	var restOfUrl = substr(url, urlProt.length);
	return (currentProt + restOfUrl);
}

function fl_addUrlParam(existingUrl, newKey, newValue)
{
	if (typeof(newValue) == 'undefined')  newValue = '';
	var newUrl = '';
	var qsa = existingUrl.split('?');
	var originalBaseUrl = '';
	var originalParams = '';
	if (qsa.length > 1)
	{
		originalBaseUrl = qsa[0];
		originalParams = fl_getUrlArgumentsAsArray(existingUrl);
	}
	else
	{
		originalBaseUrl = existingUrl;
		originalParams = [];
	}
	var newPairs = [];
	for (var currentKey in originalParams)
	{
		var currentVal = originalParams[currentKey];
		if (currentKey != newKey) // so we don't duplicate
			newPairs.push(encodeURIComponent(currentKey) + '=' + encodeURIComponent(currentVal));
	}
	newPairs.push(encodeURIComponent(newKey) + '=' + encodeURIComponent(newValue));
	newUrl = originalBaseUrl + '?' + newPairs.join('&');
	return newUrl;
}

function fl_getUrlArgumentsAsArray(url)
//	Given a url (or if omitted, the current window location,) this will return an associative array
//	representing all the URL arguments in the "query string".  If an argument has no value, the resulting
//	key will be defined but its value will be empty string.
//	ex.    .../some/Url?key1=value1&key2=value2&key3    will result in:
//		array['key1'] = 'value1'
//		array['key2'] = 'value2'
//		array['key3'] = ''
//	All keys and values are treated as strings.
// keywords...query string parse url
{
	var urlArgs = [];
	var urlQueryPortion = '';
	if (typeof(url) == 'undefined')
	{
		urlQueryPortion = window.location.search.substring(1);
	}
	else
	{
		var urlAtoms = url.split('?');
		if (urlAtoms.length > 1)
			urlQueryPortion = urlAtoms[1];
	}

	if ('' !== urlQueryPortion)
	{
		var pairs = urlQueryPortion.split("&");
		for (var i=0; i<pairs.length; i++)
		{
			var pairAtoms = pairs[i].split("=");
			var keyName = pairAtoms[0];  // split always returns at least 1 member, even if splitter was not found at all
			var value = (2 == pairAtoms.length)  ?  pairAtoms[1]  :  null;
			keyName = decodeURIComponent(keyName);
			value = decodeURIComponent(value);
			urlArgs[keyName] = value;
		}
	}

	return urlArgs;  // empty array if no args
}

function fl_buildQueryStringFromArray(ary)
//	Given an associative array of keys/values, this will return a query string 
//	beginning with the first key (*without* the leading question mark.)
//	A value of either empty string or null will result in a param with no value.
//	ex.
//		array['key1'] = 'value1'
//		array['key2'] = 'value2'
//		array['key3'] = ''
//		array['key4'] = null
//	will result in:  key1=value1&key2=value2&key3&key4
//	All keys must be strings.  All values must be strings or simple numbers, or null.
{
	var qs = [];
	for (var key in ary)
	{
		if (typeof(ary[key]) == 'function')  continue;  // IE8 array bugs

		if (null === ary[key]  ||  '' === ary[key])
			qs.push(encodeURIComponent(key));
		else
			qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(ary[key]));
	}
	return (qs.join('&'));
}

function fl_parseUrl(url)
{
	//	follows the pattern of PHP's parse_url
	var parsed = [];
	parsed.scheme = null;
	parsed.user = null;
	parsed.pass = null;
	parsed.host = null;
	parsed.port = null;
	parsed.path = null;
	parsed.query = null;
	parsed.fragment = null;

	if (null !== url  &&  '' !== url)
	{
		var tmpUrl = url;  //  http://www.google.com/path/file?query=string#fragment
		var posFragment = tmpUrl.lastIndexOf('#');
		if (posFragment >= 0)
		{
			parsed.fragment = tmpUrl.substr(posFragment+1);
			tmpUrl = tmpUrl.substr(0, posFragment);  //  http://www.google.com/path/file?query=string
		}
		var posQuery = tmpUrl.lastIndexOf('?');
		if (posQuery >= 0)
		{
			parsed.query = tmpUrl.substr(posQuery+1);
			tmpUrl = tmpUrl.substr(0, posQuery);  //  http://www.google.com/path/file
		}
		var posSchemeEnd = tmpUrl.indexOf('://');
		if (posSchemeEnd >= 0)
		{
			parsed.scheme = tmpUrl.substr(0, posSchemeEnd);
			parsed.scheme = parsed.scheme.toLowerCase();  // protocols are case-insensitive
			tmpUrl = tmpUrl.substr(posSchemeEnd+3);  //  www.google.com/path/file
		}
		if (null !== parsed.scheme)
		{
			while ('/' == tmpUrl.charAt(0))
			{
				tmpUrl = tmpUrl.substr(1);  // strip additional leading slashes from domain  (in case it was file:////...)
			}
		}
		if ('file' == parsed.scheme  ||  null === parsed.scheme)
		{
			parsed.path = tmpUrl;
		}
		else
		{
			var posFirstSlash = tmpUrl.indexOf('/');
			if (posFirstSlash >= 0)
			{
				parsed.host = tmpUrl.substr(0, posFirstSlash);
				parsed.host = parsed.host.toLowerCase();  // domains are case-insensitive
				parsed.path = tmpUrl.substr(posFirstSlash);  //  INCLUDE the slash after the domain, so "/path/file"
				tmpUrl = parsed.host;
			}
			var posPort = tmpUrl.indexOf(':');
			if (posPort >= 0)
			{
				parsed.host = tmpUrl.substr(0, posPort);
				parsed.port = parseInt(tmpUrl.substr(posPort+1),10);  //  INCLUDE the slash after the domain, so "/path/file"
			}
		}
	}

	//	alias some terms more common to JS
	if (null !== parsed.scheme)  parsed.protocol = parsed.scheme + ':';
	parsed.domain = parsed.host;
	parsed.pathname = parsed.path;
	return parsed;
}







function fl_attachEvent(elementId, eventName, functionReference)
{
	var elem = document.getElementById(elementId);
	if (null !== elem /* &&  typeof(elem['on'+eventName]) != 'undefined' */)
	{
		elem['on'+eventName] = function(evt) {
									if (typeof(evt) == 'undefined')  evt = window.event;
									return functionReference(evt);
								};
	}
}


//function fl_attachEvent(elementId, eventName, functionReference)
//{
//	var elem = document.getElementById(elementId);
//	if (null !== elem)
//	{
//		if (typeof(elem['on'+eventName]) != 'undefined'  &&  null !== elem['on'+eventName])
//		{
//			window['fl_attachEventFn_'+] = document.body.onresize;
//		}
//		else
//		{
//		}
//		elem['on'+eventName] = function(evt) {
//									if (typeof(evt) == 'undefined')  evt = window.event;
//									return functionReference(evt);
//								};
//	}
//}

function fl_safeFancyChars(str)
{
	if (typeof(str) == 'string'  &&  '' !== str)
	{
		str = str.replace(/\(R\)/g, '&reg;');
		str = str.replace(/®/g, '&reg;');
		str = str.replace(/\(C\)/g, '&copy;');
		str = str.replace(/©/g, '&copy;');
		str = str.replace(/\(TM\)/g, '&#153;');
		str = str.replace(/™/g, '&#153;');
	}
	return str;
}

function safeBool(b)
// Converts 1, "1", "true", "yes", or "on" (case insensitive) to boolean [true].  All else false.
{
    if (typeof(b) == 'boolean')
        return b;

    if (typeof(b) == 'undefined')  // undefined = false
		return false;

    if (typeof(b) == 'number')
		return(1 === b);

    if (typeof(b) == 'string')
    {
        var stringVal = b.toLowerCase();
        if (stringVal == 'true'  ||  stringVal == 'yes'  ||  stringVal == 'on'  ||  stringVal == '1')   return true;
        return false;
    }
}

function b2s(b)
{
	return((b) ? 'true' : 'false');
}






//	=========================   Form Elements   =============================



function fl_isFormElement(elementObj)
//	Returns TRUE if elementObj is any kind of "form element", by HTML standards.  This includes buttons, checkboxes, & radios.
{
	//	allow either object or ID as argument
	if (typeof(elementObj) == 'string')
		elementObj = document.getElementById(elementObj);

	if (null === elementObj)
			return false;
	if (typeof(elementObj.nodeType) == 'undefined'  ||  elementObj.nodeType != 1)  // valid node but not a valid HTML "element"
		return false;

	return(elementObj.tagName == 'INPUT'  ||  elementObj.tagName == 'SELECT'  ||  elementObj.tagName == 'TEXTAREA'  ||  elementObj.tagName == 'BUTTON');
}


function fl_isTextElement(elementObj)
//	Returns TRUE only if elementObj is a text-entry form element (text input, password, textarea.)
{
	//	allow either object or ID as argument
	if (typeof(elementObj) == 'string')
		elementObj = document.getElementById(elementObj);

	if (null === elementObj)
		return false;
	if (typeof(elementObj.nodeType) == 'undefined'  ||  elementObj.nodeType != 1)  // valid node but not a valid HTML "element"
		return false;

	return(
			(elementObj.tagName == 'INPUT'  &&  (elementObj.type == 'text'  ||  elementObj.type == 'password'))
			||  elementObj.tagName == 'TEXTAREA'
			);
}


function fl_isUserSubmittedFormElement(elementObj)
//	Returns TRUE only if elementObj is a form element type that would contain user-submitted values (not buttons, labels, fieldsets, legends, etc.)
{
	//	allow either object or ID as argument
	if (typeof(elementObj) == 'string')
		elementObj = document.getElementById(elementObj);

	if (null === elementObj)
		return false;
	if (typeof(elementObj.nodeType) == 'undefined'  ||  elementObj.nodeType != 1)  // valid node but not a valid HTML "element"
		return false;

	if (elementObj.tagName == 'INPUT')
		return (elementObj.type == 'text'  ||  elementObj.type == 'password'  ||  elementObj.type == 'checkbox'  ||  elementObj.type == 'radio');
	else
		return (elementObj.tagName == 'TEXTAREA'  ||  elementObj.tagName == 'SELECT');
}




function fl_getValueOfFormElement(elementObj)
/*
	gets current value of an input (text) box, textarea, checkbox, or select boxes (dropdowns).

	For input boxes or textareas, returns the text contents.

	For checkboxes, returns whether it is checked (true/false)

	For select/dropdowns, returns the value of the currently selected option.

	Radio buttons are left out for now -- can be problematic b/c of how they are grouped
*/
{
	//	allow either object or ID as argument
	if (typeof(elementObj) == 'string')
	{
		var elementId = elementObj;
		elementObj = document.getElementById(elementObj);
		if (null === elementObj)
		{
			
			FL_ASSERT(false, 'setValueOfFormElement("'+elementId+'", "'+myValue+'") - element not found.');
			return;
		}
	}


	//	check for undefined conditions -- do this first to prevent fatal errors in comparisons
	if (typeof(elementObj) == 'undefined'  ||  null === elementObj)  return null;  // doesn't exist
	if (typeof(elementObj.tagName) == 'undefined')  return null;  // exists but not a valid HTML element

	//	text inputs & textareas
	if ('INPUT' == elementObj.tagName)
	{
		var inputType = elementObj.type.toLowerCase();
		if 	('text' == inputType  ||  'password' == inputType)
		{
			return elementObj.value;
		}
		else if ('checkbox' == inputType  ||  'radio' == inputType)
		{
			return elementObj.checked;  // true/false
		}
	}
	else if ('TEXTAREA' == elementObj.tagName)
	{
		return elementObj.value;
	}

	//	select boxes (dropdowns)
	else if ('SELECT' == elementObj.tagName)
	{
		if(elementObj.selectedIndex >= 0 &&
			elementObj.options[elementObj.selectedIndex] !== null)
		{
			var selOption = elementObj.options[elementObj.selectedIndex];
			return selOption.value;
		}
		else
		{
			return '';
		}
	}

	//	Text nodes
	else if (elementObj.tagName == 'DIV'  ||  elementObj.tagName == 'SPAN'  ||  elementObj.tagName == 'FONT'  ||  elementObj.tagName == 'LABEL')
	{
		return(fl_getNodeText(elementObj));
	}

	//	catch-all (maybe not a good idea...)
    if (typeof(elementObj.value) != 'undefined')
    	return elementObj.value;
    else
        return null;
}

function fl_setValueOfFormElement(elementObj, myValue)
/*
	sets the value of an input (text) box, textarea, checkbox, or select box (dropdown).

	For input boxes or textareas, sets the text contents.

	For checkboxes, checks or unchecks (send myValue as true/false)

	For select/dropdowns, attempts to select the option whose value matches myValue.
*/
{
	//	allow either object or ID as argument
	if (typeof(elementObj) == 'string')
	{
		var elementId = elementObj;
		elementObj = document.getElementById(elementObj);
		if (null === elementObj)
		{
			
			FL_ASSERT(false, 'setValueOfFormElement("'+elementId+'", "'+myValue+'") - element not found.');
			return;
		}
	}

	//	check for undefined conditions -- do this first to prevent fatal errors in comparisons
	if (typeof(elementObj) == 'undefined'  ||  null === elementObj)  return;  // doesn't exist
	if (typeof(elementObj.tagName) == 'undefined')  return;  // exists but not a valid HTML element

	//	text inputs & textareas
	if ('INPUT' == elementObj.tagName)
	{
		var inputType = elementObj.type.toLowerCase();
		if 	('text' == inputType  ||  'password' == inputType)
		{
			elementObj.value = myValue;
		}
		else if ('checkbox' == inputType  ||  'radio' == inputType)
		{
			elementObj.checked = safeBool(myValue);  // true/false
		}
	}
	else if ('TEXTAREA' == elementObj.tagName)
	{
		elementObj.value = myValue;
	}

	//	select boxes (dropdowns)
	else if (elementObj.tagName == 'SELECT')
	{
		var index = getIndexOfOptionByValue(elementObj, myValue);
		if (index >= 0  &&  index < elementObj.options.length)
		{
			elementObj.selectedIndex = index;
		}
		else
		{
			elementObj.selectedIndex = -1;  // blank selection
		}
	}

	//	Text nodes (DIVs, font tags, labels)
	else if (elementObj.tagName == 'DIV'  ||  elementObj.tagName == 'SPAN'  ||  elementObj.tagName == 'FONT'  ||  elementObj.tagName == 'LABEL')
	{
 		fl_writeTextToElement(elementObj, myValue);
	}

	else
	{
		FL_ASSERT(false, 'setValueOfFormElement() - not a recognizable element type.');
	}
}

function getIndexOfOptionByValue(listBoxElement, stringToFind)
/*
	For when we know the VALUE of an option, but don't know its place in the list.
	Given a reference to a Select element, will return the index of the option
	that matches a string.
*/
{
	for (var i=0; i<listBoxElement.length; i++)
	{
		if (listBoxElement.options[i].value == stringToFind)   return(i);
	}
	return(-1);
}


function getIndexOfOptionByText(listBoxElement, stringToFind)
/*
	For when we know the TEXT of an option, but don't know its place in the list.
	Given a reference to a Select element, will return the index of the option
	that matches a string.
*/
{
	for (var i=0; i<listBoxElement.length; i++)
	{
		if (listBoxElement.options[i].text == stringToFind)   return(i);
	}
	return(-1);
}


function enableElement(elementObj, bEnable)
{
	if (typeof(elementObj) == 'undefined'  ||  null === elementObj)
		return;

	if (typeof(bEnable) == 'undefined')
		bEnable = true;;

	//	allow either object or ID as argument
	if (typeof(elementObj) == 'string')
		elementObj = document.getElementById(elementObj);

	var elemType = elementObj.type.toLowerCase();
	if ((elementObj.tagName == 'INPUT' && (elemType == 'text' || elemType == 'password' || elemType == 'checkbox' || elemType == 'radio'))
		||  (elementObj.tagName == 'TEXTAREA')
		||  (elementObj.tagName == 'SELECT'))
	{
		elementObj.disabled = !bEnable;
	}
	// labels
	else if(elementObj.tagName == 'LABEL')
	{
		elementObj.style.color = (bEnable)  ?  ''  :  'gray';
	}
	// links
	else if(elementObj.tagName == 'A')
	{
		fl_addClassToElement(elementObj, 'DISABLED');
	}
	// divs
	else if(elementObj.tagName == 'DIV')
	{
		elementObj.style.backgroundColor = 'silver';
	}
	// buttons
	else if ((elementObj.tagName == 'INPUT'  &&  elemType == 'submit')  ||  (elementObj.tagName == 'BUTTON'))
	{
		elementObj.style.color = 'gray';
		elementObj.disabled = !bEnable;
	}
}

