//---------------------------------------------------------------------
// WAF - Web/Windows Application Framework
// Copyright © 2011 The Enticy Group, LLC
//
// Notes: Miscellaneous WAF Javascript functions
//
// Revisions :
//	2003-07-08	Conrad	Added ScrollAndFocus()
//	2005-08-24	Conrad	Made "constants" of URLPARM_*
//	2005-09-05	Conrad	Added front-end adding/deleting of child table rows
//	2006-02-14	Conrad	Pulled out ERROR_MESSAGE[] and WINDOW_STYLE[] constants
//	2007-09-07	Conrad	Added putValueInIFrame()
//	2009-10-14	Roopa	Added jQuery code for handling collapse/expand of child tables.
//	2010-08-20	Conrad	Note: Use "jQuery(" in place of "$(" to avoid conflict with other Javascript libraries like Prototype
//---------------------------------------------------------------------

// We want to use "const" but IE6's version of JavaScript can't handle it
var URLPARM_ACTION = '_act';
var URLPARM_FKEYENTRY = '_fke';
var URLPARM_CHANGEDFIELDNAME = '_cfn';

var MIN_LEN_AUTO_COMP = 3; // The minimum number of characters that a person should type, before we do an auto complete
var MIN_SGST_IN_CACHE = 10; // The Minimum rows that have to exist in cache so we don't try to query the database again
var MAX_SGST_DISP = 20; // The Maximum number of rows that we are going to display at a time as suggestions.
var MAX_IN_CACHE = 5; // The Maximum Number of terms that need to be cached for each FK.  Could later on do this on a FK level, instead of a global var
var FIELDSEARCHTYPEBEGINSWITH = 1; //To figure out how to filter the data in the cache

var ERROR_MESSAGE = new Array();
ERROR_MESSAGE['NOMOREROWS'] = 'Cannot add any more rows.';

var WINDOW_STYLE = new Array();
WINDOW_STYLE['POPUP'] = 'width=800,height=420,resizable=yes,scrollbars=yes,status=yes';
WINDOW_STYLE['AUTOCOMPLETE'] = 'height=150,width=200,status=no,resizable=yes,toolbar=no,menubar=no,directories=no,location=no,scrollbars=no';
WINDOW_STYLE['HELP'] = 'width=613,height=400,resizable=yes,scrollbars=yes';
WINDOW_STYLE['EMAIL'] = 'width=700,height=500,resizable=yes,scrollbars=yes,status=yes,menu=yes';
WINDOW_STYLE['PRINT'] = 'width=700,height=500,resizable=yes,scrollbars=yes,status=yes,menu=yes';
WINDOW_STYLE['ANYPOPUPDEFAULT'] = 'width=600,height=400,status=no,resizable=no,toolbar=no,menubar=no,directories=no,location=no,scrollbars=no';

// This function is used by foreign keys to call the popup form
function FKPopup(href, field, displayfield) {
	if (field.type == 'text') {
		if (field.value != displayfield.value) href += '&' + URLPARM_FKEYENTRY + '=' + field.value;
	}
	EditPopup(href);
}

// This function is used to call the popup form
function EditPopup(href) {
	var hWnd = window.open(href, '', WINDOW_STYLE['POPUP']);
	if ((document.window != null) && (!hWnd.opener)) hWnd.opener = document.window;
	if (window.focus) hWnd.window.focus();
}

// This function is called to put a value into a foreign key field
function FKUpdate(inputfield, displayfield, valuefield, keyvalue, displayvalue) {
	FieldUpdate(inputfield, displayvalue, displayvalue);
	FieldUpdate(displayfield, displayvalue, displayvalue);
	FieldUpdate(valuefield, keyvalue, displayvalue);
}

// This function is called to put a value into any field
function FieldUpdate(field, value, displayvalue) {
	if (field) {
		if (field.type == 'text') {
			field.value = value;
		} else if (field.type == 'hidden') {
			field.value = value;
		} else {
			// Loop through the existing options trying to find the given key
			var bOptionFound = false;
			for (var i = 0; i < field.length; i++) {
				if (field.options[i].value == value) {
					field.options[i].selected = true;
					bOptionFound = true;
				}
			}
			// If we didn't find it, then we need to add a new one.
			if (!bOptionFound) {
				var oOption = new Option(displayvalue, value, true, true);
				field.options[field.length] = oOption;
			}
		}
	}
}

// This function is called to put data into a valid value field
function VVSet(field, value) {
	if ((field.type == 'text') || (field.type == 'hidden')) {
		field.value = value;
	} else if (field.type == 'select-one') {
		// Loop through the existing options trying to find the given value
		var bOptionFound = false;
		for (var i = 0; i < field.length; i++) {
			if (field.options[i].value == value) {
				field.options[i].selected = true;
				bOptionFound = true;
			}
		}
		// If we didn't find it, then we need to add a new one.
		if (!bOptionFound) {
			var oOption = new Option(value, value, true, true);
			field.options[field.length] = oOption;
		}
	} else {
		// radio buttons
		for (var i = 0; i < field.length; i++) {
			if (field[i].value == value) {
				field[i].checked = true;
			}
		}
	}
}

// This function is called to put data into a valid value field
function VVCopy(tofield, fromfield) {
	if ((fromfield.type == 'text') || (fromfield.type == 'hidden') || (fromfield.type == 'select-one')) {
		VVSet(tofield, fromfield.value);
	} else {
		// radio buttons
		for (var i = 0; i < fromfield.length; i++) {
			if (fromfield[i].checked == true) {
				VVSet(tofield, fromfield[i].value);
			}
		}
	}
}

// This function is called to set the value of a Boolean field
function BoolSet(field, value) {
	if ((field.type == 'text') || (field.type == 'hidden')) {
		field.value = value;
	} else {
		// checkbox
		if (field.value == value) {
			if (!field.checked) {
				// Execute the click(), rather than just changing field.checked.  That way, any event handlers are called.
				field.click();
				//field.checked = true;
			}
		} else {
			if (field.checked) {
				field.click();
				//field.checked = false;
			}
		}
	}
}

// This function is called to copy data between Boolean fields
function BoolCopy(tofield, fromfield) {
	if ((fromfield.type == 'text') || (fromfield.type == 'hidden')) {
		BoolSet(tofield, fromfield.value);
	} else {
		// fromfield is a checkbox
		if (fromfield.checked) {
			BoolSet(tofield, fromfield.value);
		} else {
			BoolSet(tofield, 0);
		}
	}
}

// This function is called for each keydown in a foreign key
function FKKeydownHandler(event, fnAutocomplete, fnChoice, fnNew, fnNavigate) {
	// gets the code for NS or IE
	var keycode = event.keyCode ? event.keyCode :
                event.which ? event.which : event.charCode;
	// We've chosen "Tab" as the autocomplete character, 
	// F2 for choice, F3 for navigate, and F4 for new
	if (keycode == 9 && fnAutocomplete) {
		fnAutocomplete();
		return false;
	} else if (event.keyCode == 113 && fnChoice) {
		fnChoice();
		cancelEvent(event);
		return false;
	} else if (event.keyCode == 114 && fnNavigate) {
		fnNavigate();
		cancelEvent(event);
		return false;
	} else if (event.keyCode == 115 && fnNew) {
		fnNew();
		cancelEvent(event);
		return false;
	} else {
		return true;
	}
}

function cancelEvent(event) {
	if (event.which) {
		// Mozilla 1.3 required to get rid of processing of F3 key
		needToCancelFunctionKey = true;
	} else {
		// IE 6.0 Required to get rid of processing of F3 key
		event.keyCode = 0;
		event.returnValue = false;
	}
}


// This function invokes the autocomplete function by popping up a
// window which will put the value back into the given field.
function FKAutocomplete(field, displayfield, parms) {
	if (field.type == 'text') {
		if (field.value != '' && field.value != displayfield.value) {
			var href = 'WAFPopup.aspx?' + URLPARM_ACTION + '=Autocomplete&' + parms + '&' + URLPARM_FKEYENTRY + '=' + field.value;
			var windowname = '_blank';
			window.open(href, windowname, WINDOW_STYLE['AUTOCOMPLETE']);
		}
	}
}

// This utility function opens a help window
function ShowHelp(url) {
	var hWnd = window.open(url, 'HelpWindow', WINDOW_STYLE['HELP']);
	if (window.focus) hWnd.window.focus();
}

// This function is used to call the email popup window 
function EmailPopup(href) {
	var hWnd = window.open(href, '', WINDOW_STYLE['EMAIL']);
	if (window.focus) hWnd.window.focus();
}

// This function is used to call the print popup window 
function PrintPopup(href) {
	var hWnd = window.open(href, '', WINDOW_STYLE['PRINT']);
	if (window.focus) hWnd.window.focus();
}

// This function is used to popup any window 
function AnyPopup(href, name, windowStyles) {
	if (!name) name = '_blank';
	if (!windowStyles) windowStyles = WINDOW_STYLE['ANYPOPUPDEFAULT'];
	var hWnd = window.open(href, name, windowStyles);
	if (window.focus) hWnd.window.focus();
}

// Trigger the print dialog 
function PrintPage() {
	if (window.print) {
		self.focus();
		self.print();
	}
}

// Functions to support page scrolling and item positioning. Used in handling ReDisplay.
// From http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
function getScrollXY() {
	var horizontalScroll = 0, verticalScroll = 0;
	if (typeof (window.pageYOffset) == 'number') {
		//Netscape compliant
		verticalScroll = window.pageYOffset;
		horizontalScroll = window.pageXOffset;
	} else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
		//DOM compliant
		verticalScroll = document.body.scrollTop;
		horizontalScroll = document.body.scrollLeft;
	} else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
		//IE6 standards compliant mode
		verticalScroll = document.documentElement.scrollTop;
		horizontalScroll = document.documentElement.scrollLeft;
	}
	return [horizontalScroll, verticalScroll];
}

// From http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
function getSizeXY() {
	var viewPortWidth = 0, viewPortHeight = 0;
	if (typeof (window.innerWidth) == 'number') {
		//Non-IE
		viewPortWidth = window.innerWidth;
		viewPortHeight = window.innerHeight;
	} else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
		//IE 6+ in 'standards compliant mode'
		viewPortWidth = document.documentElement.clientWidth;
		viewPortHeight = document.documentElement.clientHeight;
	} else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
		//IE 4 compatible
		viewPortWidth = document.body.clientWidth;
		viewPortHeight = document.body.clientHeight;
	}
	return [viewPortWidth, viewPortHeight];
}

// Function invoked on dropdown option change to re-display a form.
// Submits the form with added values to the form action to specify 
// the action and identify the field
function ReDisplayForm(field) {

	// We can only redisplay a form if the field is in a form to begin with.
	if (field.form != null) {
		var ActionURL = field.form.action;
		var addUrl = URLPARM_ACTION + '=ReDisplay&' + URLPARM_CHANGEDFIELDNAME + '=' + field.name + ',' + getScrollXY()[0] + ',' + getScrollXY()[1];

		// Make sure we didn't already add the ReDisplay action.
		// The change event can arrive fast, this will prevent re-entry.
		if (ActionURL.indexOf(URLPARM_ACTION + '=ReDisplay') > 0)
			return false;

		if (ActionURL.indexOf('?') > 0)
			ActionURL += '&' + addUrl;
		else
			ActionURL += '?' + addUrl;

		field.form.action = ActionURL;
		field.form.submit();
	}

	return false;
}

// Called after ReDisplay, this function is used to scroll the 
// page back to the same position from before the redisplay and
// set focus back to the field which triggered the redisplay.
function ScrollAndFocus(field, horizontalScroll, verticalScroll) {
	if (field) {
		window.scrollTo(horizontalScroll, verticalScroll);

		if (field.type != 'hidden') {
			if (field.focus) {
				field.focus();
			}
		}
	}
}

/* Not yet ready for prime-time
// This function is called to submit a given form to a given URL.
// It us used this way: <a href="foo" onclick="return doActionLink('form_id', this.href, 0);">Submit</a>
function doActionLink(formId, href, bPopup) {
// Although a GET can go to an unnamed URL, a POST cannot. (It would generate a 405 error.)
// (You could also get a 405 error if you were trying to POST to an ".html" page.)
if (href.substring(0,1) == '?') {
var sPath = window.location.pathname;
href = sPath.substring(sPath.lastIndexOf('/') + 1) + href;
}
if (bPopup == 1) {
href = 'javascript:ShowHelp(\'' + href + '\')' 
}
//alert(href);
document.forms[formid].action = href; 
document.forms[formid].submit(); 
return false;
}
*/

// This function is called to visually mark a row as deleted.  The actual deleting is not done here.
function toggleRowDeleted(row) {
	toggleClassName(row, 'deleted');
};

// This function finds the first row in a table that is classed with "hidden" and changes it to normal.
function unhideRow(table) {
	toggleNextRow(table, 'hidden', ERROR_MESSAGE['NOMOREROWS']);
}

// This function will toggle the classname of an object (or add the second class if both are missing)
function toggleClassName(e, cn) {
	if (containsToken(e.className, cn, ' ')) {
		e.className = removeToken(e.className, cn, ' ');
	} else {
		e.className = addToken(e.className, cn, ' ');
	}
}

// This function finds the first row in a table that has a given class and changes it to another class.
// If it finds no such row, it can give the user an alert.
function toggleNextRow(table, cls, msg) {
	var rows = table.getElementsByTagName('tbody')[0].getElementsByTagName('tr');
	for (var i = 0; rows[i] != null; i++) {
		if (containsToken(rows[i].className, cls, ' ')) {
			rows[i].className = removeToken(rows[i].className, cls, ' ');
			break;
		}
	}
	if (rows[i] == null) {
		if (msg != null) alert(msg);
	}
}

function containsToken(str, tok, delim) {
	return (delim + notNull(str) + delim).indexOf(delim + tok + delim) != -1
}

function addToken(str, tok, delim) {
	if (containsToken(str, tok, delim)) {
		return str;
	} else {
		if (notNull(str) == '') {
			return tok;
		} else {
			return str + delim + tok;
		}
	}
}

function removeToken(str, tok, delim) {
	var tempstr = delim + notNull(str) + delim;
	tempstr = tempstr.replace(delim + tok + delim, delim);
	return tempstr.substring(delim.length, tempstr.length - 2 * delim.length + 1);
}

function notNull(str) {
	if (str == null) {
		return '';
	} else {
		return str;
	}
}

// Used to write an HTML value into an IFRAME (for HTML_VIEW_IN_IFRAME fields)
function putValueInIFrame(iframe, html) {
	var oDoc;
	if (document.frames) {
		// Non-standard, but most compatible method -- uses object's NAME
		oDoc = document.frames[iframe].document;
	} else {
		// Standards-compliant method -- uses object's ID
		oDoc = document.getElementById(iframe).contentDocument;
	}
	try {
		oDoc.open();
		oDoc.write(html);
		oDoc.close();
	} catch (e) {
		//alert(ERROR_MESSAGE['IFRAMEWRITEFAILED']);
	}
}

// shows "processing..." instead of specified button/div/span
// NOTE: function expects fully-qualified jQuery expression to match elements:
//		* div -- to match all <div> elements
//		* div.abc -- to match all <div class="abc"> elements
//		* #xyz -- to match all elements with id="xyz"
function actionDisplayProcessing(hide, show, animationImgId) {
	jQuery(hide).hide();
	jQuery(show).show();

	// re-animate image
	// (reset image source to display animated image vs. greyed-out/no-image -- needed for some browsers, especially IE)
	var code = "var img = document.getElementById('" + animationImgId + "'); if(img){ img.src = img.src; }";
	setTimeout(code, 100);

	return true;
}
/**************************************************************************************
Simple Function for debugging. Writes to the text area that we generally use for debugging XML
Will need to modify the xslt to make that textarea available, so we can see the 
text being written out to it.
**************************************************************************************/
function logTodebug(message) {
	jQuery("textarea[name='debug']").prepend(message + '\r\n')
}

/***********************************************************************************************
The function that sends the AJAX call to get the values for the term that the person entered.
The Term that the person entered is passed in as a parameter(request.term).
The response object is passed in so, it can be written to on the successful return of the AJAX call.  
The Element is the actual input item that the user is messing around with.
This function expects that the element will have a hidden sibling with the id SugURLInput
which will contain the URL to the page that will supply the suggestions.  
The AJAX call expects the URL to be sent down without a query string (!!NEED TO CHECK AGAIN!!)
as such, it is split into the URL and the data being sent down and the term is tacked on to the
front of the data values.   When the call returns, the function first caches the data sent down.
The cache is a queue (JS array) with a maximum = MAX_IN_CACHE items at any time. 
The cached array is held on to using jquery.data and associated with the element that the data belongs to.
Finally, the function makes the call to map the data into the requisite structure for the autocomplete. 
The function displays an alert with the msg should any errors occur.
***********************************************************************************************/
function getSuggestionsFromDB(term, response, element) {
	var sURL = jQuery(element).siblings("input.SugURLInput:first").attr("value");
	if (sURL) {
		var splitURL = sURL.split("?");
		var sData = 'term=' + encodeURIComponent(term); // URL encode the term so nothing barfs
		if (splitURL.length == 2) 
			sData = sData + '&' + splitURL[1];
		jQuery.ajax({
			url: splitURL[0],
			data: sData,
			dataType: "json",
			success: function(ret) {
				if (ret) {
					if (!ret.data) {
						alert(ret);
					} else {
						var cache = element.data('autocompleteCache') || new Array();
						if (cache.length == MAX_IN_CACHE) {
							cache.shift();
							// Debug code - Uncomment next line AND comment the previous line
							//logTodebug(term + ' - Clearing ' + cache.shift().term + ' from the queue.');
						}

						cache.push( { term: term.toLowerCase(), results: ret } );
						element.data('autocompleteCache', cache);
						response(mapSuggestData(ret.data, term, ret.tst, false)); // Already filtered data, so we do not require to filter it
					}
				}
			},
			// More of a catch all for the programmer.
			error: function(xhr, ajaxOptions, thrownError) {
				if (thrownError) alert(thrownError);
				else if (xhr) alert(xhr.status + ' - ' + xhr.statusText);
				else if (ajaxOptions) alert(ajaxOptions);
				else alert('The suggestions AJAX call failed');
			}
		});
	}
}

/**************************************************************************************************
Function that attempts to get the suggestions data from the local cache.  
Parameter: Element is the element input that the autocomplete is being performed on
Parameter: Term is the value that the person has entered

This function first retrieves all the cached values for the element.  
Now, the cache is an array of objects with cache.term = term that the AJAX call was made for,
and the results = to the JSON object returned by the AJAX call.
For a lengthy name like "Johansson" for instance we might have more than one AJAX call that might be made and cached.
We try to find the cache object entry that has the term = to the maximum possible characters in the term that the user has just entered.  
Say if the person has entered "Johansso". We first look to see if Johansso has been cached.  If not we proceed to do a substring - 1 on the 
term and look for Johanss in the cache.  If that is not found, we proceed to look for Johans in the cache.  
We continue doing the substring and look for an exact match of the term.  This loop will be performed until we either get a match
or an the sub-stringed term length is less than the minLength set for that element.
If we find an entry in the cache that is the exact match of the (sub-stringed) term, we proceed to filtering the data
and return a properly mapped object for autocomplete to work with.
**************************************************************************************************/
function getSuggestionsFromCache(term, element) {

	var cache = element.data('autocompleteCache') || {},
		i = term.length,
		cachedValues = {};
	if (cache.length) {
		var minlength = jQuery(element).autocomplete("option", "minLength"); //Since the minlength can be tinkered around with, we cant trust the variable
		while (i >= minlength) {
			var subTerm = term.substring(0, i--).toLowerCase();
			// Could do an .each iterator, instead of grep, but found this to be slightly cleaner eliminating the need
			// to break out of the .each iterator
			var cd = jQuery.grep(
				cache,
				function(value) {
					return (value.term == subTerm);
				}
			);
			if (cd.length) {
				var cachedResults = cd[0].results;
				cachedValues.hasMoreResults = cachedResults.hmr;
				// Can't do a direct count on the number of items in the data, because the list
				// will need to be filtered to match the current term.  
				// Since we are going to iterate through each one of the items, we might as well
				// map it.  Will save on the call to map later.
				cachedValues.data = mapSuggestData(cachedResults.data, term, cachedResults.tst, true);
				break;
			}
		}
	}
	return cachedValues;
}

/*******************************************************************************************************************
Function that takes the data (JSON object) sent down from the server and maps it into a correct array for the autocomplete to work.
In addition to the data itself, this function takes the following additional parameters:
This also only maps as many values as is set in the MAX_SGST_DISP - so we don't end up
displaying a 1000 values even if we fetch 1000. 

Parameter: term is another parameter that is required so we know what to filter the rows on
Parameter: searchType is also passed in so we know how to filter the data in the cache.  If the search was conducted using 
  BeginsWith and then we make sure the regular expression is formed as such. All other types of search default to the Contains regular expression.
Parameter: needFilter to filter the values in the list if necessary.  This is required of cached values, so we only pick up
  pertinent rows from the cache.
**************************************************************************************************/
function mapSuggestData(data, term, searchType, needFilter) {
	var retValues = new Array();
	var count = 1;
	var matcher;
	if (searchType == FIELDSEARCHTYPEBEGINSWITH)
		matcher = new RegExp("^" + jQuery.ui.autocomplete.escapeRegex(term), "i");  // Begins with and ignore case
	else
		matcher = new RegExp(jQuery.ui.autocomplete.escapeRegex(term), "i");  // ignore case
	jQuery.each(
		data,
		function(key, value) {
			if (count > MAX_SGST_DISP) return null;
			if (!needFilter || (needFilter && matcher.test(value))) {
				retValues.push( { key: key, value: value } );
				count++;
			}
		}
	);
	return retValues;
}
/******************************************************************************************************
Will highlight the first occurence of the term passed in, in the value passed in.
The actual highlighting is done using CSS.  The code here simply adds a span with a CSS class.  The span will
belong to the class whose name will be the id of the element that the suggest list belongs to.  The CSS applies
a default style with the class name ac_term which is ALWAYS applied to the span.
*******************************************************************/
function highlight(value, term, elementid) {
	return value.replace(
		new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + jQuery.ui.autocomplete.escapeRegex(term) + ")(?![^<>]*>)(?![^&;]+;)", "i"),
		"<span class='" + elementid + " ac_term'>$1</span>"
	);
}

// jQuery Validate integration - Adds jQuery handlers for forms which request validation.
//if (window.jQuery) {
//	jQuery(document).ready(function() {
//		// Apply validation to all forms marked that way.
//		jQuery("form.validated").validate({
//			errorClass: "InError",
//			errorElement: "div"
//		});
//	});
//}

// jQuery UI integration - Adds jQuery handlers for various UI elements after document loads.
if (window.jQuery) {
	jQuery(document).ready( function() {
		// Apply datepicker functionality to all requested date fields.
		var i = jQuery("input.ShowDateChoice");
		if (i.datepicker) {
			i.datepicker({
				showButtonPanel: true, // add a "done" button that you can click to *not* pick a date
				changeMonth: true, // add a drop-down for the month
				changeYear: true, // add a drop-down for the year
				showOn: 'button', // 'focus', 'button', or 'both': 'button' adds a button after the field
				buttonImage: 'waf/images/calendar.png', // the images to display if we are adding a button to click on to activate the calendar
				buttonImageOnly: true, // true makes the button be an image only, not an bordered button
				buttonText: 'pick a date', // hover text on the "choose" button
				duration: 0, // milliseconds, display immediately
				yearRange: '-100:+20', // show a broad range of years
				constrainInput: false // allow the user to type in the field without validation (allows for entering time)
			});
		}
		// Apply child table collapse/expand to all requested child tables.
		jQuery("a.CollapseLink").click(function() {
			// Show or hide the child table, with an effect
			// Note: this still depends on the structure that the child table DIV follows the secondary title DIV.  Should be fixed.
			jQuery(this).parent().next(".Collapsible").slideToggle("normal").toggleClass("Collapsed");
			jQuery(this).parent().toggleClass("Collapsed");
			// Look for the next collapse state input and toggle it.
			jQuery(this).siblings("input.collapsestate:first").each(function(idx) { jQuery(this).val(3 - jQuery(this).val()); });
			// Returns false so that the click does not navigate to the href. 
			return false;
		});
		// Child table "Add new row" functionality.
		jQuery("a.NewRow").click(function() {
			// Expand the child table if it is collapsed.
			if (jQuery(this).parent().hasClass("Collapsed")) {
				jQuery(this).parent().next(".Collapsible").slideToggle("normal").toggleClass("Collapsed");
				jQuery(this).parent().toggleClass("Collapsed");
			}
			// Need to store the row in a variable, so we can complain if no more rows can be added.
			var h = jQuery(this).parent().next().find("table.RecordTable tr.hidden:first");
			if (jQuery(h).length)
				jQuery(h).removeClass("hidden");
			else
				alert(ERROR_MESSAGE['NOMOREROWS']);
			return false;
		});

		if (jQuery.tablesorter) {
			jQuery("table.sortable").tablesorter({
				// define a custom text extraction function 
				textExtraction: function(node) {
					var retVal;
					// extract sorting data from cell and return it 
					if ($("input:checkbox", node).length == 1) {
						// cell contains a checkbox, so use the fact that it is checked or not
						retVal = (jQuery("input:checkbox", node).is(":checked")) ? "0" : "1";
					}
					else if ($("input:file", node).length == 1) {
						// a file input field is upload-only, so its value is irrelevant; use the text
						retVal = jQuery(node).text();
					}
					else if ($("input", node).length == 1) {
						// cell contains an input field, so use the value
						retVal = jQuery("input", node).val();
					}
					else if ($("select", node).length == 1) {
						// cell contain a select (dropdown) field, so use the selected display value
						retVal = jQuery("select option:selected", node).text();
					}
					else {
						// readonly fields should have inner text
						retVal = jQuery.trim(jQuery(node).text());
					}
					return retVal;
				}
			});
		}

		if (jQuery.ui && jQuery.ui.autocomplete) {
			jQuery(".FKeySuggestTextControl").autocomplete({ minLength: MIN_LEN_AUTO_COMP,
				source: function(request, response) {
					var element = this.element; // To get a handle to the input, we HAVE to do this.element.  'this' in this particular context is supposed to be the jQuery object
					// First check the cache.  Suggestions will be returned filtered for the term.  
					// If hasMoreResults is 0 that means there are no more results in the db
					// and there is really no use querying the db once again. If there are no more results
					// or the number of suggestions in cache is greater than minimum requirement, we
					// don't need to look any further
					var cacheValues = getSuggestionsFromCache(request.term, element); //Call returns an object.  Need 2 values, the data and if there are more results
					if (cacheValues.data) {
						if (cacheValues.data.length >= MIN_SGST_IN_CACHE || cacheValues.hasMoreResults == 0) {
							//logTodebug(request.term + ' - Cached data');
							response(cacheValues.data);
							return; // Found in cache, bail out.
						}
					}
					// Either not enough cached values or was not found in cache.  Don't care what the case is
					// just call to fetch the records from db.
					//logTodebug(request.term + ' - Fetching data');
					getSuggestionsFromDB(request.term, response, element);
				},
				select: function(e, ui) {
					// Note: 'this' gives you the handle to the input.  Unlike in the source area - don't get it...  
					if (this) {
						var fldName = this.id.split("_Input")[0];
						FKUpdate(this, document.getElementById(fldName + '_Display'), document.getElementById(fldName), ui.item.key, ui.item.value);
					}
				}
			});

			// @@ROOPA - this is a required hack (Monkey Patch) to highlight the text correctly in autocomplete lists
			// when using jQuery 1.8.6. The code there does .append( $( "<a></a>" ).text( item.label ) ) 
			// which makes it escape the strong or bold tags  
			jQuery.ui.autocomplete.prototype._renderItem = function(ul, item) {
				return $("<li></li>")
					.data("item.autocomplete", item)
					.append('<a>' + highlight(item.label, this.term, $(this.element).attr('id')) + '</a>')
					.appendTo(ul);
			};

		}

	});
}

/* 
Applications can override this default datepicker settings by changing styles and adding jQuery code.
Example: to show a calendar button on the right of the field and only show the datepicker on button click,
override the style for datecontrol to not display a background image, 
and add the following jscript:
		
jQuery.datepicker.setDefaults({
showOn: 'button',
buttonImageOnly: true,
buttonImage: 'waf/images/calendar.png',
buttonText: 'pick a date'
});
*/

