var m_objCallerDocRef = document;
var ID_IFRAME_AUTO_SUGGEST = "ifrAutoSuggest";	
var ID_IFRAME_DISPLAY_ITEMS = "fraDisplayItems";

//////////////////////////////////////////////////////////////////////////////////
//	CMqAutoSuggest Class
//	Dependencies: CMqFormElement, Window.js, CMqEventUtils
//////////////////////////////////////////////////////////////////////////////////
function CMqAutoSuggest()
{
	var m_blnVisible=false;
	var m_strBoundControlID = "";
	var m_strOriginalText = "";
	var m_strOriginalValue = "";
	var m_strUpdateSearchRetries = 0; 
	var m_strPathToDisplayChoicesASP;
	var KEY_ENTER = 13;
	var KEY_TAB = 9;
	var KEY_SHIFT = 16;
	var KEY_BACKSPACE = 8;
	var KEY_ARROW_UP = 38;
	var KEY_ARROW_DOWN = 40;
	var KEY_ARROW_LEFT = 37;
	var KEY_ARROW_RIGHT = 39;
	
	var ATTRIBUTE_MQVALUE = "mqvalue";
	var ATTRIBUTE_ALLOW_NOT_IN_LIST = "mqallownotinlist";
	var ATTRIBUTE_AUTOSUGGESTOFF = "mqautosuggestoff";
	var VALUE_INVALID_MQVALUE = "";
	var NUM_MAX_RESULTS_PER_PAGE = 25;
	var MAXIMUM_MS_TO_WAIT_FOR_UPDATE = 2000;
	var RETRY_DELAY_UPDATE_SEARCH = 25;
	var CSS_UNIT_PIXELS = "px";
	var m_objCallerDocRef;
	var m_objEventUtils = new CMqEventUtils();

	//Used when repositioning/sizing the frame:
	var BOUND_CONTROL_BORDER_SIZE = 1;
	var BOUND_CONTROL_SIZE = 25; //includes padding
	var AUTOSUGGEST_WIDTH_MAX = 400;
	var AUTOSUGGEST_HEIGHT_MAX = 170;
	
	//AUTOSUGGEST EVENTS - START/////////////////////////////////////////////////////////////////////////////////////
	function OnBlur(objElement){

		WriteConsole("[AutoSuggest.OnBlur] Bound control '" + objElement.id + "' and value '" + objElement.value + "'");
		
		if(objElement){
			if(!ElementIsReadOnly(objElement)){
				if(!IsVisible()){
					WriteConsole("[AutoSuggest.OnBlur] Validating...");
					ValidateBoundControlValue(true);
				}
			}
		}
	}
	
	function OnFocus(objElement, strFieldID, strFieldCaption, e, strTable, strDataStore, lngDtsFieldID, strCO){
	
		WriteConsole("[AutoSuggest.OnFocus] Bound control '" + objElement.id + "' and value '" + objElement.value + "'");
		
		var blnAbort = false;
		
		if(objElement){
		
			if(!ElementIsReadOnly(objElement)){
			//take note of original values.

				if(!IsVisible()){
				//Very important - only execute this logic when the dialog is loaded. Why?
				//We want to take a snapshot of the field value before it is influenced by the AutoSuggest dialog.
				
					if(objElement.getAttribute(ATTRIBUTE_AUTOSUGGESTOFF) != "1"){
						//Do we have a bound conrol ID?
						if(m_strBoundControlID == objElement.id){
							//After Hiding the frame, the controlID is blank.
							//If not blank, it must have been set to something.
							//Don't do anything - we already have the info we need.
							blnAbort = true;
						}
						
						if(!blnAbort){
							
							//We weren't told to abort - that means the bound control we know about is different
							//from the one were' focusing on now.
							//Is current one valid? (should be)
							if(m_strBoundControlID != ""){
								
								//Restore original values if needed - we need to clean up before we use the new focused control.
								ValidateBoundControlValue();
							}
							
							//Now that the other control has been concluded...
							//Take note of the values.
							m_strBoundControlID = objElement.id;	
							m_strOriginalValue = objElement.getAttribute(ATTRIBUTE_MQVALUE);
							m_strOriginalText = objElement.value;
							
							WriteConsole("[AutoSuggest.OnFocus] Bound control '" + objElement.id + "' with value '" + objElement.value + "' and mqvalue '" + m_strOriginalValue + "'");
							
							SetBoundControlMqValue(VALUE_INVALID_MQVALUE);
							
							if(objElement.value.length > 0){
								//Focus and select the text.
								//We figure users will typically want to replace their selection
								// - rather than alter it by a few characters.
								objElement.focus();
								objElement.select();
							}else{						
								WriteConsole("[AutoSuggest.OnFocus] Field value was empty... Showing auto suggest frame...");
						
								Show(objElement, strFieldID, strFieldCaption, strTable, strDataStore, e, lngDtsFieldID, strCO);
								
								WriteConsole("[AutoSuggest.OnFocus] Waiting to update the frame with the search value '" + objElement.value + "'");
								
								UpdateSearchWhenReady(e, objElement);
							}
						}
					}
						
				}else{
					//The AutoSuggest dialog is visible, and we received another focus event?
					//Make sure the focus event came from the bound control.
					WriteConsole("[AutoSuggest.OnFocus] Checking to see if I should hide the suggest frame");
					
					if(!ElementMatchesBoundControl(objElement)){
						//Not the same ID? They somehow focused on another control.
						//Nice try! Hide the auto complete frame.
						Hide(false, "AutoSuggest.OnFocus");
						
						OnFocus(objElement, strFieldID, strFieldCaption, e, strTable, strDataStore, lngDtsFieldID, strCO);
					}
				}
			}
		}
	}
	
	//Called whenenever the bound control receives an "onkeyup" event
	function OnKeyUp(objElement, strFieldID, strFieldCaption, event, strTable, strDataStore, lngDtsFieldID, strCO){

		WriteConsole("[AutoSuggest.OnKeyUp] Bound control '" + objElement.id + "' and value '" + objElement.value + "'");
		
		if(objElement){
			if(!ElementIsReadOnly(objElement)){
				if(!IsVisible()){
					WriteConsole("[AutoSuggest.OnKeyUp] AutoSuggest frame isn't visible and loaded yet.");
					WriteConsole("[AutoSuggest.OnKeyUp] Is search value '" + objElement.value + "' >= 1 character?");
					
					if(objElement.value.length >= 0){
					
						if(m_objEventUtils){
							var keynum = m_objEventUtils.GetKeyCode(event);
							if(keynum!=KEY_TAB && keynum!=KEY_SHIFT){ //don't trigger the search when tabbing in if there 
								WriteConsole("[AutoSuggest.OnKeyUp] Yes... Showing auto suggest frame...");
								Show(objElement, strFieldID, strFieldCaption, strTable, strDataStore, event, lngDtsFieldID, strCO);
								
								WriteConsole("[AutoSuggest.OnKeyUp] Waiting to update the frame with the search value '" + objElement.value + "'");
								UpdateSearchWhenReady(event, objElement);
							}else{
								WriteConsole("[AutoSuggest.OnKeyUp] Not searching because was tabbed into and there is a value in the field");
							}
						}
					}
				
				}else{
					WriteConsole("[AutoSuggest.OnKeyUp] Autosuggest frame is already visible and loaded.");
					WriteConsole("[AutoSuggest.OnKeyUp] Waiting to update the frame with the search value '" + objElement.value + "'");
					UpdateSearchWhenReady(event, objElement);
				}
			}
		}
	}

	//called whenenever the bound control receives an "onkeydown" event
	function OnKeyDown(objElement, event)
	{
		if(objElement){
			if(!ElementIsReadOnly(objElement)){
				
				//Trigger a focus event. 
				OnFocus(objElement);
			
				WriteConsole("[AutoSuggest.OnKeyDown] Bound control '" + objElement.id + "' and value '" + objElement.value + "'");
				if(IsVisible()){
					HandleSpecialKeys(objElement, event);	
				}
			}
		}
	}
	
	function OnClick(objElement, e){
		if(objElement){
			if(ElementMatchesBoundControl(objElement)){
				if(!ElementIsReadOnly(objElement)){
					//Cancel the event here, or it will bubble up and trigger an Record body onclick, which will
					//hide the AutoSuggest frame.
					if(m_objEventUtils){
						m_objEventUtils.CancelEventBubble(e, true);
					}
				}
			}
		}
	}
	
	
	//AUTOSUGGEST EVENTS - END/////////////////////////////////////////////////////////////////////////////////////	
	
	
	//AUTOSUGGEST ACTIONS - START//////////////////////////////////////////////////////////////////////////////////
	function Initialize(strPathToDisplayChoices, objCallerDocRef, arrHideEventElements)
	{
		m_strPathToDisplayChoicesASP = strPathToDisplayChoices;
		m_objCallerDocRef = objCallerDocRef;
		
		if(arrHideEventElements){
			RegisterHideEvents(arrHideEventElements);
		}
	}
	
	function Show(objElement, strFieldID, strFieldCaption, strTable, strDataStore, event, lngDtsFieldID, strCO)
	{
		if(m_objEventUtils){
			var keynum = m_objEventUtils.GetKeyCode(event);
			
			if(!IsVisible() && keynum != KEY_ENTER){
				var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);

				if(objAutoSuggestFrame){
				
					Hide(false, "Show");//Hide the frame if it was previously shown.
				
					if(objElement){
						SetPosition(objElement);
				
						if(objElement){
							//Using some special options here:
							//	DGH ----> Disable Grid Header. Will ensure the grid header does not appear.
							//	DCB ----> Disable Cell Borders. Does not add a css border to the table cells.
							//									The AutoSuggest frame already has a border.
							
							if(typeof(strCO)=="undefined"){
								strCO="0";
							}
							var strURL = m_strPathToDisplayChoicesASP + 'WCDisplayChoices.asp?WCI=DisplayChoices&TBL=' + strTable + '&DS=' + strDataStore + '&CO=' + strCO + '&SV=' + objElement.value + '&DGH=1&DCB=1';
							
							WriteConsole("[AutoSuggest.Show] Navigating to '" + strURL + "'");
							
							if(typeof VNChoiceField !== "undefined") {
								var objField = new CMqField(strFieldID,null,null,null,null,null,null,strFieldCaption);
								var objFormElement = new CMqFormElement();
								objFormElement.Initialize(objElement);
								var objChoiceField = new VNChoiceField(objField, objFormElement, strTable, false);
								objChoiceField.Open(strURL, objAutoSuggestFrame, NUM_MAX_RESULTS_PER_PAGE);
							} else {
								var strAutoSuggestURL = LoadLargeListOrCombo(strFieldID, strFieldCaption, strURL,strTable, true, NUM_MAX_RESULTS_PER_PAGE, lngDtsFieldID);
							}
						}
						
						if(strAutoSuggestURL){
							if(objAutoSuggestFrame.contentWindow){
								if(objAutoSuggestFrame.contentWindow.window){
									if(objAutoSuggestFrame.contentWindow.window.location){
										if(objAutoSuggestFrame.contentWindow.window.location.replace){
											objAutoSuggestFrame.contentWindow.window.location.replace(strAutoSuggestURL);
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
	
	//Triggered when the Record body fires an onresize event.
	function Reposition()
	{
		if(IsVisible()){
			var objBoundControl = m_objCallerDocRef.getElementById(m_strBoundControlID);
			
			if(objBoundControl){
				SetPosition(objBoundControl);
			}
		}
	}
	
	function SetPosition(objBoundControl)
	{
		var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);	
		
		if(objAutoSuggestFrame){
		
			var objFormElement = new CMqFormElement();	
			
			if(objBoundControl){
				var lngXPos = objFormElement.GetPosition(objBoundControl, true);
				var lngYPos = objFormElement.GetPosition(objBoundControl, false);
				var lngWidthWithIFrame = (lngXPos + AUTOSUGGEST_WIDTH_MAX);
				
				//Determine if the iframe will go beyond the X contraints:
				if(lngWidthWithIFrame > GetWinWidth()){
					lngXPos = lngXPos - (AUTOSUGGEST_WIDTH_MAX-objBoundControl.offsetWidth+BOUND_CONTROL_BORDER_SIZE);
				}else{
					objAutoSuggestFrame.style.width = AUTOSUGGEST_WIDTH_MAX + CSS_UNIT_PIXELS;
					objAutoSuggestFrame.style.height = AUTOSUGGEST_HEIGHT_MAX + CSS_UNIT_PIXELS;
				}
				objAutoSuggestFrame.style.top = (lngYPos + (BOUND_CONTROL_SIZE + (BOUND_CONTROL_BORDER_SIZE*2))) + CSS_UNIT_PIXELS;
				objAutoSuggestFrame.style.left = lngXPos + CSS_UNIT_PIXELS;
			}
		}	
	}
	

	function UpdateSearchWhenReady(e, objElement)
	{

		try{
			//If UpdateSearchWhenReady is called via the setTimeout below, the "event" parameter is lost. IE seems to be losing
			//the "event" object as it was when this process originated. To prevent this, we manually create
			//a copy of the event here. IE doesn't mess with the event this way.
			//http://javascriptfixer.com/member-not-found.php
			if(m_strUpdateSearchRetries == 0){
				var eCopy = {};
				for (var i in e){
					eCopy[i] = e[i];
				}			
				e = eCopy;
			}
			
		}catch(err){}//if this fails, try to continue as usual.
			
		
		var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);
		
		if(objAutoSuggestFrame){
			if(objAutoSuggestFrame.contentWindow){
				objAutoSuggestFrame = objAutoSuggestFrame.contentWindow;
				
				WriteConsole("[AutoSuggest.UpdateSearchWhenReady] Bound control '" + objElement.id + "'");
				
				//Have we reached the maximum retries allowed?
				//2000 divided by 25 = 80 retries
				if(m_strUpdateSearchRetries < (MAXIMUM_MS_TO_WAIT_FOR_UPDATE / RETRY_DELAY_UPDATE_SEARCH)){

					//Make sure the grid is fully loaded before we update the search value.
					if(!objAutoSuggestFrame.UpdateSearch || !IsVisible()){
						WriteConsole("[AutoSuggest.UpdateSearchWhenReady] Waiting... " + m_strUpdateSearchRetries + ";IsVisible=" + IsVisible());
						
						if(!objAutoSuggestFrame.UpdateSearch)
							WriteConsole("[AutoSuggest.UpdateSearchWhenReady] Reason: UpdateSearch wasn't there.");
							
						if(!IsVisible())
							WriteConsole("[AutoSuggest.UpdateSearchWhenReady] Reason: AutoSuggest was not visible.");
						
						m_strUpdateSearchRetries = m_strUpdateSearchRetries + 1;
						
						setTimeout(function() {UpdateSearchWhenReady(e, objElement)}, RETRY_DELAY_UPDATE_SEARCH);
					}else{
						WriteConsole("[AutoSuggest.UpdateSearchWhenReady] Ready to go! Calling AutoSuggest_UpdateSearch...");
						
						m_strUpdateSearchRetries = 0;
						UpdateSearch(e, objElement);
					}
				}else{
					WriteConsole("[AutoSuggest.UpdateSearchWhenReady] Reached maximum retries - had to abort. Retries: " +  m_strUpdateSearchRetries);
					m_strUpdateSearchRetries = 0;
					
					//Ok, the retries count was higher than the limit. Something is wrong with the AutoSuggest frame, most likely.
					//Terminate it... we may otherwise be dealing with an infinite loop.
					Hide(false, "AutoSuggest.UpdateSearchWhenReady");
				}
			}
		}
	}
	
	//As the name suggests, we use this method to determine if we should apply the best match
	//before the autosuggest frame is hidden.
	function AttemptLastMinuteMatch(blnLastMinuteMatchRequiresText)
	{
		var blnAbort = false;
		var objElement;
		
		if(IsVisible()){
			WriteConsole("[AutoSuggest.AttemptLastMinuteMatch] Evaluating... Match requires text? " + blnLastMinuteMatchRequiresText + " (" + typeof(blnLastMinuteMatchRequiresText)  + ")");
			var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);
			var objDisplayItemsFrame = GetDisplayItemsFrameObject();

			if(objDisplayItemsFrame){
			
				if(blnLastMinuteMatchRequiresText){
					WriteConsole("[AutoSuggest.AttemptLastMinuteMatch] Making sure that the bound control has text... ");
					objElement = m_objCallerDocRef.getElementById(m_strBoundControlID);
					
					if(objElement){
						//Only restore the values if something is in the textbox.
						//Maybe they want to wipe field out - we naturally allow this.
						if(objElement.value.length <= 0){
							WriteConsole("[AutoSuggest.AttemptLastMinuteMatch] Bound control didn't have any text. Abort!");
							blnAbort = true;
						}else{
							WriteConsole("[AutoSuggest.AttemptLastMinuteMatch] Bound control had text, proceeding as usual.");
						}
					}
				}
				
				if(!blnAbort){
					//If at least one record is shown, assume they wanted to select it.
					//It is, after all, the best match according to what they were typing.
					if(objAutoSuggestFrame.contentWindow.GetTotalRecordCount() >= 1){	
						
						WriteConsole("[AutoSuggest.AttemptLastMinuteMatch] Selecting current value...");
						
						//Select this value before the frame is hidden.
						if(objDisplayItemsFrame.DblClickCurrentRow){
							objDisplayItemsFrame.DblClickCurrentRow(null,null,true);
						}
					}
				}
			}
		}
	}
	
	function ValidateBoundControlValue()
	{
		WriteConsole("[AutoSuggest.ValidateBoundControlValue] Consider replacing the original value if necessary");
		
		if(m_strBoundControlID != ""){		
		
			//Consider replacing the original value if necessary...
			var objElement = m_objCallerDocRef.getElementById(m_strBoundControlID);
			
			if(objElement){
				//Is the MqValue still invalid?
				if(objElement.getAttribute(ATTRIBUTE_MQVALUE) == VALUE_INVALID_MQVALUE){
					
					//Only restore the values if something is in the textbox.
					//Maybe they want to wipe field out - we naturally allow this.
					if(objElement.value.length > 0){
						
						WriteConsole("[AutoSuggest.ValidateBoundControlValue] Current values:'" + objElement.value + "' and mqvalue '" + objElement.getAttribute(ATTRIBUTE_MQVALUE) + "'");						
						WriteConsole("[AutoSuggest.ValidateBoundControlValue] Restoring previous text:'" + m_strOriginalText + "' and value '" + m_strOriginalValue + "'");
						
						if(objElement.getAttribute(ATTRIBUTE_ALLOW_NOT_IN_LIST) != "1"){
							//It is - restore the previous value.
							objElement.value = m_strOriginalText;
							objElement.setAttribute(ATTRIBUTE_MQVALUE, m_strOriginalValue);
						}
					}
				}
			}
			
			//Wipe everything out - we have no more use for the bound control information.
			m_strOriginalText = "";
			m_strOriginalValue = "";
			m_strBoundControlID = "";
		}
	}
	
	//Called from many places.
	//For example, all frames visible in the main HelpDesk/Census interface will call this method as per their
	//onclick event being triggered. These events are registered by TmplLookupItemCompact.htm.
	function Hide(blnAttemptLastMinuteMatch, strSource, blnLastMinuteMatchRequiresText)
	{	
		var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);

		if(IsVisible()){
			WriteConsole("[AutoSuggest.Hide] I've been ordered to terminate the auto complete frame by '" + strSource + "'. Attempt Last Minute Match? " + blnAttemptLastMinuteMatch + " (" + typeof(blnAttemptLastMinuteMatch)  + ")");

			//Because of the way we register the event listeners in TmplLookupItemCompact.htm, this method is sometimes called with
			//different parameters than we'd expect. Firefox sometimes sends in the mouse event as the first parameter, for example.
			//This means blnAttemptLastMinuteMatch is a mouse event object, and since the event is a valid object, 
			//the if(blnAttemptLastMinuteMatch) call would return true, attempting a last minute match in a totally invalid scenario.
			//To ensure this never happens, we first ensure the variable is a boolean.
			if(typeof(blnAttemptLastMinuteMatch) == 'boolean'){
				if(blnAttemptLastMinuteMatch){
					AttemptLastMinuteMatch(blnLastMinuteMatchRequiresText);
				}
			}

			if(objAutoSuggestFrame){
				objAutoSuggestFrame.style.display = "none";
				SetVisibleState(false);
			}
			
			ValidateBoundControlValue();
		}else{
			WriteConsole("[AutoSuggest.Hide] Was already hidden, no work done.");
		}
	}

	function Display(){
		var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);

		if(objAutoSuggestFrame){
			objAutoSuggestFrame.style.display = "";
		}
	}
	
	function HandleSpecialKeys(objElement, e)
	{
		if(m_objEventUtils){
			var keynum = m_objEventUtils.GetKeyCode(e);
			
			var objDisplayItemsFrame = GetDisplayItemsFrameObject();
			
			WriteConsole("[AutoSuggest.HandleSpecialKeys] key=" + keynum);
			
			switch(keynum)
			{
				case KEY_BACKSPACE: 
					WriteConsole("[AutoSuggest.HandleSpecialKeys] BACKSPACE");
					UpdateSearchWhenReady(e, objElement);
				break;
				
			  case KEY_TAB:
					WriteConsole("[AutoSuggest.HandleSpecialKeys] TAB");
					
					//User pressed tab. They may have a valid option selected, will check.
					//However, if the user pressed tab without having any text in the box, that should abort
					//the last minute match.
					Hide(true, "AutoSuggest.HandleSpecialKeys - TAB", true);
				break;
				
			  case KEY_ENTER: 
					WriteConsole("[AutoSuggest.HandleSpecialKeys] ENTER");
					
					//User pressed Enter. They may have a valid option selected, will check.
					Hide(true, "AutoSuggest.HandleSpecialKeys - ENTER", false);
					
					//Cancel the event here, as it has been forwarded to the auto suggest frame.
					//Without these lines, we hear 4 dings in IE.
					m_objEventUtils.CancelEventBubble(e);				
				break;

			  case KEY_ARROW_UP: 
				
				WriteConsole("[AutoSuggest.HandleSpecialKeys] ARROW UP");
				
				if(IsVisible()){
					var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);
					
					if(objAutoSuggestFrame){
						if(objAutoSuggestFrame.contentWindow){
							objAutoSuggestFrame = objAutoSuggestFrame.contentWindow;
							
							if(objAutoSuggestFrame.GetTotalRecordCount() > 0){	
								
								if(objDisplayItemsFrame){
									if(objDisplayItemsFrame.SelectPreviousRow){
										objDisplayItemsFrame.SelectPreviousRow();
									}
								}
								
								if(objDisplayItemsFrame){
									if(objDisplayItemsFrame.DblClickCurrentRow){
										objDisplayItemsFrame.DblClickCurrentRow(null,null,true);
										
										//Cancel the event here, as it has been forwarded to the auto complete frame.
										//Without these lines, we hear 4 dings in IE.
										m_objEventUtils.CancelEventBubble(e);
									}
								}
							}
						}
					}
				}

				break;

			  case KEY_ARROW_DOWN: 
				
				WriteConsole("[AutoSuggest.HandleSpecialKeys] ARROW DOWN");
				
				if(IsVisible()){
			
					var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);
						
					if(objAutoSuggestFrame){
						if(objAutoSuggestFrame.contentWindow){
							objAutoSuggestFrame = objAutoSuggestFrame.contentWindow;

							if(objAutoSuggestFrame.GetTotalRecordCount() > 0){	
								
								if(objDisplayItemsFrame){
									if(objDisplayItemsFrame.SelectNextRow){
										objDisplayItemsFrame.SelectNextRow();
									}
									
								
									if(objDisplayItemsFrame.DblClickCurrentRow){
										objDisplayItemsFrame.DblClickCurrentRow(null,null,true);
										
										m_objEventUtils.CancelEventBubble(e);
									}
								}
							}
						}
					}
				}
				break;
			}
		}
	}
	
	function UpdateSearch(e, objElement)
	{	
		WriteConsole("[AutoSuggest.UpdateSearch] Start'");
		
		if(objElement){
			WriteConsole("[AutoSuggest.UpdateSearch] m_strBoundControlID=" + m_strBoundControlID);
			WriteConsole("[AutoSuggest.UpdateSearch] objElement.id=" + objElement.id);
			
			if(IsVisible() && ElementMatchesBoundControl(objElement)){
				
				var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);
				
				if(objAutoSuggestFrame.contentWindow){
					objAutoSuggestFrame = objAutoSuggestFrame.contentWindow;
				}
				if(m_objEventUtils){
					var keynum = m_objEventUtils.GetKeyCode(e);

					if(objAutoSuggestFrame){
						if(keynum != KEY_ARROW_DOWN && keynum != KEY_ARROW_UP && keynum != KEY_ARROW_LEFT && keynum != KEY_ARROW_RIGHT){
							var strSearchVal = objElement.value;
						
							if(objAutoSuggestFrame.UpdateSearch){
							
								WriteConsole("[AutoSuggest.UpdateSearch] calling update search query with the text '" + strSearchVal + "'");
								
								objAutoSuggestFrame.UpdateSearch(e, strSearchVal);

								if(objAutoSuggestFrame.GetTotalRecordCount() == 0 || strSearchVal == ""){	
									SetBoundControlMqValue(VALUE_INVALID_MQVALUE);
								}
							}
						}
					}
				}
			}else{
				WriteConsole("[AutoSuggest.UpdateSearch] Abort - either frame wasn't loaded, or the element sent differs from the bound control. Element we received had a name of '" + objElement.id + "' and a value of '" + objElement.value + "'");
				Hide(false, "AutoSuggest.UpdateSearch");
			}
		}
	}

	function ElementMatchesBoundControl(objElement){
		var blnMatch = false;
		
		if(objElement){
			if(m_strBoundControlID == objElement.id){
				blnMatch = true;
			}
		}
		
		return blnMatch;
	}
	
	function SetBoundControlMqValue(strValue){
	
		var objElement = m_objCallerDocRef.getElementById(m_strBoundControlID);
		
		if(objElement){
			objElement.setAttribute(ATTRIBUTE_MQVALUE,strValue);
		}
	}
	
	function SetVisibleState(blnVisible){
		m_blnVisible = blnVisible;
	}
	
	function IsVisible(){
		return m_blnVisible;
	}
	
	function SetOriginalValue(strValue){
		m_strOriginalValue = strValue;
	}
	
	function SetOriginalText(strText){
		m_strOriginalText = strText;
	}
	
	function RegisterHideEvent(objFrame){
		try
		{
			var objDoc=objFrame;
			if(objFrame){
				if(objFrame.contentWindow){
					if(objFrame.contentWindow.document){
						objDoc=objFrame.contentWindow.document;	
					}
				}
			}
			
			if(objDoc){
				if(document.all){
					objDoc.attachEvent('onclick', this.AutoSuggest_Hide);
				}else{
					objDoc.addEventListener('click', this.AutoSuggest_Hide, false);
				}
			}
		}catch(e){
		}
	}
	
	function RegisterHideEvents(arrHideEventElements)
	{
		var ID_TABS = "Tabs";
		var objElementTemp;
		var strExternalCall;
		var objTabManager;
		var intTotalTabs;
		var objTab;

		if(arrHideEventElements){
			for(var i=0;i<arrHideEventElements.length;i++){
				objElementTemp = arrHideEventElements[i];
				
				if(objElementTemp){
					RegisterHideEvent(objElementTemp);
					
					//This next part is very specific and should eventually be more generic.
					//Rather be specific than make assumptions, at this point in the code.
					if(objElementTemp.id == ID_TABS){
						strExternalCall = "if(document){";
						strExternalCall += "if(document.getElementById('Record')){";
						strExternalCall += "if(document.getElementById('Record').contentWindow){";
						strExternalCall += "if(document.getElementById('Record').contentWindow.AutoSuggest_Hide){";
						strExternalCall += "document.getElementById('Record').contentWindow.AutoSuggest_Hide();";
						strExternalCall += "}}}};";	

						if(objElementTemp){
							if(objElementTemp.contentWindow){
								if(objElementTemp.contentWindow.m_objTabsMgr){
									objTabManager = objElementTemp.contentWindow.m_objTabsMgr;
									intTotalTabs = objTabManager.TabCount();
									
									for(var lngCount=0; lngCount<=intTotalTabs ; lngCount++){
										objTab = objTabManager.GetTabFromID(lngCount);

										if(objTab && !objTab.Hidden){
											objTab.SetOnClick(strExternalCall);
										}
									}
								}
							}
						}
					}
				}
				
				//make sure the object handle is never allowed to live.
				objElementTemp = null;
			}
		}

	}

	function GetBoundControlID()
	{
		return m_strBoundControlID;
	}

	//AUTOSUGGEST ACTIONS - END//////////////////////////////////////////////////////////////////////////////////
	
	//Events
	this.OnBlur = OnBlur;
	this.OnFocus = OnFocus;
	this.OnKeyUp = OnKeyUp;
	this.OnKeyDown = OnKeyDown;
	this.OnClick = OnClick;
	
	//Actions
	this.Initialize = Initialize;
	this.Reposition = Reposition;
	this.Hide = Hide;
	this.Display = Display;
	this.SetVisibleState = SetVisibleState;
	this.IsVisible = IsVisible;
	this.SetOriginalValue = SetOriginalValue;
	this.SetOriginalText = SetOriginalText;
	this.SetBoundControlMqValue = SetBoundControlMqValue;
	this.GetBoundControlID=GetBoundControlID;
}

//////////////////////////////////////////////////////////////////////////////////
//	CMqAutoSuggestSettings Class
//////////////////////////////////////////////////////////////////////////////////
function CMqAutoSuggestSettings(strCaption,strTableName,strFieldName,lngDSType)
{
	
	function GetCaption(){
		return strCaption;
	}
	
	function GetTableName(){
		return strTableName;
	}
	
	function GetFieldName(){
		return strFieldName;
	}
	
	function GetDSType(){
		return lngDSType;
	}
	
	this.GetCaption = GetCaption;
	this.GetTableName = GetTableName;
	this.GetFieldName = GetFieldName;
	this.GetDSType = GetDSType;
}

//////////////////////////////////////////////////////////////////////////////////
//	CMqAutoSuggestSwitcher Class
//////////////////////////////////////////////////////////////////////////////////
function CMqAutoSuggestSwitcher()
{
	var m_arrSettings = new Array();
	var m_objSettingsActive;
	
	function Add(objSettings, lngID){
		m_arrSettings[lngID] = objSettings;
	}
	
	function MakeActive(lngID){
		m_objSettingsActive = m_arrSettings[lngID];
		return GetActive();
	}	
	
	function GetActive(){
		return m_objSettingsActive;		
	}
	
	this.MakeActive = MakeActive;
	this.GetActive = GetActive;
	this.Add = Add;
}

//////////////////////////////////////////////////////////////////////////////////
//	Misc methods
//////////////////////////////////////////////////////////////////////////////////

	function ElementIsReadOnly(objElement){
		var blnResult = false;
		
		if(objElement){
			if(objElement.readOnly){
				blnResult = true;
			}else{
				blnResult = false;
			}
		}
		
		return blnResult;
	}

	
	
	function GetDisplayItemsFrameObject() //pass doc to function?
	{
		var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);
		var objAutoSuggestFrameDoc;
		var objDisplayItemsFrame;
		
		if(objAutoSuggestFrame.contentWindow){
			objAutoSuggestFrameDoc = objAutoSuggestFrame.contentWindow.document;
			
			if(objAutoSuggestFrameDoc){
				var objDisplayItemsFrame = objAutoSuggestFrameDoc.getElementById(ID_IFRAME_DISPLAY_ITEMS);
				
				if(objDisplayItemsFrame){
					if(objDisplayItemsFrame.contentWindow){
						return objDisplayItemsFrame.contentWindow;
					}
				}
			}
		}
	}
	
	function AutoSuggest_Hide()
	{
		if(m_objAutoSuggest){
			m_objAutoSuggest.Hide();
		}
	}
	
	//Triggered when the actual DisplayChoices/DisplayUsers list has finished loading.
	function OnListFinishedLoading()
	{
		WriteConsole("[OnListFinishedLoading] Visible and Loaded!");

		if(m_objAutoSuggest){
			//Now that we know the list of options has loaded, make the frame visible.
			m_objAutoSuggest.Display();

			m_objAutoSuggest.SetVisibleState(true);
			
			var objAutoSuggestFrame = m_objCallerDocRef.getElementById(ID_IFRAME_AUTO_SUGGEST);
			
			if(objAutoSuggestFrame){
				if(objAutoSuggestFrame.contentWindow){
					objAutoSuggestFrame = objAutoSuggestFrame.contentWindow;
					
					//Make sure the paging buttons make sense.
					if(objAutoSuggestFrame.HandleButtonStates){
						objAutoSuggestFrame.HandleButtonStates();
					}
				}
			}
		}
	}
	
	//todo: register this method in a manner that allows multiple instances to co-exist
	//This method is called whenever the auto suggest frame changes the value of the bound control (textbox).
	//Typically this means the user has selected a valid value - either explicitly by clicking it, or pressing tab/enter -
	// or implicitly by using the arrow keys to navigate through the list.
	function OnSetListToCaller(strFieldID, arrSelectedIDs, arrSelectedValues){
		
		if(m_objAutoSuggest){
			if(m_objAutoSuggest.IsVisible()){
		
				WriteConsole("[OnSetListToCaller] Valid option selected! ID '" + arrSelectedIDs + "', Value '" + arrSelectedValues + "'");
				
				m_objAutoSuggest.SetOriginalValue(arrSelectedIDs);
				m_objAutoSuggest.SetOriginalText(arrSelectedValues);
				
				WriteConsole("[OnSetListToCaller] Adopting that new value as the valid fallback value");
				
				m_objAutoSuggest.SetBoundControlMqValue("");
			}
		}
	}
	
	//todo: register this method in a manner that allows multiple instances to co-exist
	//This method is called whenever the user double-clicks a cell in the autosuggest list of values.
	function OnDblClickItem(strName, strID, blnKeepAutoSuggestFrameOpen){
	
		if(m_objAutoSuggest){
			if(!blnKeepAutoSuggestFrameOpen){
				m_objAutoSuggest.Hide(false, "OnDblClickItem");
			}
		}
	}
	
	//todo: register this method in a manner that allows multiple instances to co-exist
	//This method is called whenever the user single-clicks a cell in the autosuggest list of values.
	//We treat any form of mouse-click as if it was a double-click.
	//This has the added side-effect of blocking people from right-clicking and messing with the iframe 
	//directly.
	function OnClickItem(strName, strID, strSrcControl){
		var blnContinue=true;
		var strBoundCtrl="";
		var objDisplayItemsFrame;
		
		if(m_objAutoSuggest){
			if(strSrcControl!=""){	
				strBoundCtrl=m_objAutoSuggest.GetBoundControlID();
				if(strBoundCtrl!=""){
					//if they aren't the same control, don't continue
					if(strBoundCtrl!=strSrcControl){
						blnContinue=false;
					}
				}
			}
			if(blnContinue){
				if(m_objAutoSuggest.IsVisible()){
			
					objDisplayItemsFrame = GetDisplayItemsFrameObject();

					if(objDisplayItemsFrame){		
						if(objDisplayItemsFrame.DblClickCurrentRow){
							objDisplayItemsFrame.DblClickCurrentRow(strName,strID,false);
						}
					}

				}
			}
			m_objAutoSuggest.Hide(false, "OnClickItem");
		}
	}