﻿var AutoComplete = new function() {

    /* Auto Complete Control: Events Information */
    //
    // The events currently supported are:
    //
    //     AC_BeforeQuery() - called before each query request (useful to get query parameters when required)
    //     AC_OnChange(iValue) - called when the value changes
    //
    // To use the events you must define the functions on the textbox control you wish to use them on.


    /* Event Handlers */
    this.OnKeyUp = function(oEvent, oTextbox) {

        //test whether the text has changed and if so, clear the hidden value
        if (oTextbox.AC_KeyDownText != f.GetValue(oTextbox)) {
            AutoComplete.ClearValue(oTextbox);
        }

        //get the keycode
        var iKeyCode = f.GetKeyCodeFromEvent(oEvent);

        //bomb out if it's one of the cursor moving keys etc... (but not if it's space or backspace)
        if (iKeyCode <= 40 && iKeyCode != 32 && iKeyCode != 8) return;

        //if we have more than one char, wait until the user stops typing then check whether to search or not
        if (f.GetValue(oTextbox).length > 1) {

            oTextbox.AC_Search = function() { AutoComplete.CheckSearch(oTextbox); };

            if (oTextbox.AC_Timeout) clearTimeout(oTextbox.AC_Timeout);
            oTextbox.AC_Timeout = setTimeout(oTextbox.AC_Search, 300);
        } else {
            AutoComplete.HideResults(oTextbox);
        }

    }

    this.OnKeyDown = function(oEvent, oTextbox) {

        //get the associated objects we need
        var oParams = AutoComplete.GetParams(oTextbox);
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');

        //get the keycode
        var iKeyCode = f.GetKeyCodeFromEvent(oEvent);

        //if the results are visible, work out what we want to do
        if (f.Visible(oResultsDiv)) {

            switch (iKeyCode) {

                case 38: //up arrow
                    AutoComplete.Move(oTextbox, -1);
                    return;

                case 40: //down arrow
                    AutoComplete.Move(oTextbox, 1);
                    return;

                case 33: //page up
                    AutoComplete.Move(oTextbox, -6);
                    return;

                case 34: //page down
                    AutoComplete.Move(oTextbox, 6);
                    return;

                case 27: //escape
                    AutoComplete.HideResults(oTextbox);

                    oTextbox.AC_RestoreOldSelection = function() {
                        AutoComplete.RestoreOldSelection(oTextbox);

                        //update our test variable for the next key up
                        oTextbox.AC_KeyDownText = f.GetValue(oTextbox);
                    };
                    setTimeout(oTextbox.AC_RestoreOldSelection, 300);

                    return;

                case 9: //tab

                    //if there's only one item in the box then select it
                    var iSelected = AutoComplete.GetSelectedIndex(oTextbox);
                    if (iSelected == -1 && oResultsDiv.childNodes.length == 1) {
                        iSelected = 0;
                    }

                    //update the hidden controls with the selection
                    AutoComplete.Select(oTextbox, iSelected);

                    //update our test variable ready for key up
                    oTextbox.AC_KeyDownText = f.GetValue(oTextbox);

                    return;

                case 13: //enter

                    //if there's only one item in the box then select it
                    var iSelected = AutoComplete.GetSelectedIndex(oTextbox);
                    if (iSelected == -1 && oResultsDiv.childNodes.length == 1) {
                        iSelected = 0;
                    }

                    //update the hidden controls with the selection
                    if (iSelected >= 0) AutoComplete.Select(oTextbox, iSelected);

                    //update our test variable ready for key up
                    oTextbox.AC_KeyDownText = f.GetValue(oTextbox);

                    //cancel the default behaviour of the event
                    oEvent.cancelBubble = true;
                    oEvent.returnValue = false;
                    return false;
            }

        }

        //update our test variable ready for key up
        oTextbox.AC_KeyDownText = f.GetValue(oTextbox);

    }

    this.OnFocus = function(oEvent, oTextbox) {
        AutoComplete.SaveSelection(oTextbox);
    }




    /* Check Search */
    this.CheckSearch = function(oTextbox) {

        //if more than two chars then search, else get rid of the 'working' class
        if (f.GetValue(oTextbox).length > 2) {
            f.AddClass(oTextbox, 'working');
            AutoComplete.SendRequest(oTextbox);
        } else {
            f.RemoveClass(oTextbox, 'working');
        }
    }




    /* Results Handling */
    this.ShowResults = function(oTextbox) {
        var oParams = AutoComplete.GetParams(oTextbox);
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');

        //show results div and set focus on the textbox
        f.Show(oResultsDiv);
        //oTextbox.style.position = 'relative'; //not sure whether we need this (what is it for?)
        f.SetFocus(oTextbox);

        //set position and width of results
        if (oParams.Top != 0 || oParams.Left != 0) {
            e.SetTopLeft(oResultsDiv, oParams.Top, oParams.Left);
        } else {
            e.SetTopLeft(oResultsDiv, oTextbox.offsetTop + oTextbox.offsetHeight - 1, oTextbox.offsetLeft);
        }
        oResultsDiv.style.width = (oTextbox.offsetWidth - 2) + 'px';

        //hide obscured controls (for IE6 only)
        AutoComplete.ShowHideObscuredControls(oTextbox, false);

        //attach the events that get triggered when we click out of the results or on the textbox using the mouse
        if (oTextbox.AC_ClickOutOfResultsEvent == undefined) {
            oTextbox.AC_ClickOutOfResultsEvent = f.AttachEvent(document, 'click', function() {
                if (f.GetValue(oTextbox) == '') {
                    AutoComplete.ClearValue(oTextbox);

                    //check for postback
                    if (oParams.AutoPostback && iIndex >= 0) {
                        Postback(oTextbox.id, 0);
                    } else if (!oParams.AutoPostback && oTextbox.AC_OnChange) {
                        oTextbox.AC_OnChange(f.GetValue(oTextbox.id + '_Value'));
                    }
                } else {
                    oTextbox.AC_RestoreOldSelection = function() {
                        AutoComplete.RestoreOldSelection(oTextbox);

                        //update our test variable for the next key up
                        oTextbox.AC_KeyDownText = f.GetValue(oTextbox);
                    };
                    setTimeout(oTextbox.AC_RestoreOldSelection, 300);
                }

                AutoComplete.HideResults(oTextbox);
            });
        }
        if (oTextbox.AC_ClickOnTextboxEvent == undefined) {
            oTextbox.AC_ClickOnTextboxEvent = f.AttachEvent(oTextbox, 'click', function(oEvent) { oEvent.cancelBubble = true; });
        }

    }
    this.HideResults = function(oTextbox) {
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');

        //hide results div
        f.Hide(oResultsDiv);

        //show obscured controls (for IE6 only)
        AutoComplete.ShowHideObscuredControls(oTextbox, true);

        //detach the events for clicking away from the results with the mouse
        if (oTextbox.AC_ClickOutOfResultsEvent != undefined) f.DetachEvent(oTextbox.AC_ClickOutOfResultsEvent);
        if (oTextbox.AC_ClickOnTextboxEvent != undefined) f.DetachEvent(oTextbox.AC_ClickOnTextboxEvent);
        oTextbox.AC_ClickOutOfResultsEvent = undefined;
        oTextbox.AC_ClickOnTextboxEvent = undefined;

    }

    this.ShowHideObscuredControls = function(oTextbox, bShowObscuredControls) {
        var oParams = AutoComplete.GetParams(oTextbox);

        //don't bother unless this is ie6
        if (b.IE6()) {

            var oControl;
            for (var i = 0; i < oParams.ShowHideControls.length; i++) {

                oControl = f.GetObject(oParams.ShowHideControls[i]);
                if (oControl != null) {

                    if (!bShowObscuredControls) {
                        oControl.style.visibility = 'hidden';
                    } else {
                        oControl.style.visibility = '';
                    }
                }
            }
        }
    }

    this.Move = function(oTextbox, iAmount) {
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');

        var iItemCount = oResultsDiv.childNodes.length;
        var iCurrentSelectedIndex = AutoComplete.GetSelectedIndex(oTextbox);
        var iNewSelectedIndex = iCurrentSelectedIndex + iAmount;

        if (iItemCount == 0) {
            return;
        } else if (iNewSelectedIndex < 0) {
            iNewSelectedIndex = 0;
        } else if (iNewSelectedIndex > iItemCount - 1) {
            iNewSelectedIndex = iItemCount - 1;
        }

        AutoComplete.ChangeSelection(oTextbox, iNewSelectedIndex, iCurrentSelectedIndex);
    }

    this.ChangeSelection = function(oTextbox, iSelectIndex, iDeselectIndex) {
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');

        if (iDeselectIndex >= 0) f.RemoveClass(oResultsDiv.childNodes[iDeselectIndex], 'selected');
        f.AddClass(oResultsDiv.childNodes[iSelectIndex], 'selected');

        if (oResultsDiv.childNodes[iSelectIndex].offsetTop < oResultsDiv.scrollTop) {
            oResultsDiv.scrollTop = oResultsDiv.childNodes[iSelectIndex].offsetTop;
        } else if (oResultsDiv.childNodes[iSelectIndex].offsetTop + oResultsDiv.childNodes[iSelectIndex].offsetHeight >
                oResultsDiv.scrollTop + oResultsDiv.offsetHeight) {
            oResultsDiv.scrollTop = oResultsDiv.childNodes[iSelectIndex].offsetTop + oResultsDiv.childNodes[iSelectIndex].offsetHeight - oResultsDiv.offsetHeight;
        }
    }

    this.Select = function(oTextbox, iIndex) {
        var oParams = AutoComplete.GetParams(oTextbox);
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');
        var oValueControl = f.GetObject(oTextbox.id + '_Value');

        if (iIndex >= 0) {

            //get the display value to select and process it to get rid of HTML
            var sDisplayValue = f.GetHTML(oResultsDiv.childNodes[iIndex]);
            sDisplayValue = sDisplayValue.replace(/<span>|<\/span>/gi, '');
            sDisplayValue = sDisplayValue.replace(/&amp;/gi, '&');
            if (sDisplayValue.indexOf('<') >= 0) sDisplayValue = sDisplayValue.substring(0, sDisplayValue.indexOf('<'));

            //set the selection
            f.SetValue(oTextbox, sDisplayValue);
            f.SetValue(oValueControl, oResultsDiv.childNodes[iIndex].AC_ItemValue);

            //save it
            AutoComplete.SaveSelection(oTextbox);
        } else {
            AutoComplete.Clear(oTextbox);
        }

        //check for postback
        if (oParams.AutoPostback && iIndex >= 0) {
            Postback(oTextbox.id, 0);
        } else if (!oParams.AutoPostback && oTextbox.AC_OnChange) {
            oTextbox.AC_OnChange(f.GetValue(oTextbox.id + '_Value'));
        }

        //hide the results
        AutoComplete.HideResults(oTextbox);

    }




    /* Clever Webservice Stuff */
    this.SendRequest = function(oTextbox) {
        var oSearchService = new WebService();

        oSearchService.Go = function() {
            if (oTextbox.AC_BeforeQuery) oTextbox.AC_BeforeQuery();
            var oParams = AutoComplete.GetParams(oTextbox);
            
            var aWebServiceParams = new Array(['eSourceQuery', oParams.SourceQuery], ['sQueryParams', oParams.QueryParams.join(',')],
                ['sSearch', f.GetValue(oTextbox)]);
            this.RunWebService(oParams.URL, 'http://intuitivesystems', 'Search', aWebServiceParams, this, false);
        }

        oSearchService.Done = function(oXMLResponse) {
            var sResponse = this.GetTagValue(oXMLResponse, 'SearchResult');
            AutoComplete.DisplayResults(oTextbox, sResponse);
        }

        oSearchService.Go();
    }

    this.DisplayResults = function(oTextbox, sResponse) {
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');

        //get results
        var aResults = sResponse.split('|');

        //clear out old results
        while (oResultsDiv.hasChildNodes()) oResultsDiv.removeChild(oResultsDiv.firstChild);

        //loop through and draw out results
        for (var i = 0; i < aResults.length; i++) {
            var sDisplayValue = aResults[i++];
            var sValue = aResults[i];

            //create a div for each one and attach an onclick event to it
            //the GetClickEvent() function might seem like a waste of time, but it's not - it's exploiting javascript closures...
            var oResultItemDiv = document.createElement('div');
            f.AttachEvent(oResultItemDiv, 'click', AutoComplete.GetClickEvent(oTextbox, (i - 1) / 2));

            //set the values
            f.SetHTML(oResultItemDiv, sDisplayValue);
            oResultItemDiv.AC_ItemValue = sValue;

            //append it
            oResultsDiv.appendChild(oResultItemDiv);
        }

        //remove 'working' class from the textbox
        f.RemoveClass(oTextbox, 'working');

        //show results
        AutoComplete.ShowResults(oTextbox);

    }

    this.GetClickEvent = function(oTextbox, iIndex) {
        var oFunction = function(oEvent) {
            AutoComplete.Select(oTextbox, iIndex);
            oEvent.cancelBubble = true;
        }

        return oFunction;
    }




    /* Support Functions */
    this.GetParams = function(oTextbox) {

        var oResult = new Object();

        var oParamsControl = f.GetObject(f.SafeObject(oTextbox).id + '_Params');
        var oQueryParamsControl = f.GetObject(f.SafeObject(oTextbox).id + '_QueryParams');
        var oShowHideControlsControl = f.GetObject(f.SafeObject(oTextbox).id + '_ShowHideControls');

        var aParams = f.GetValue(oParamsControl).split('|');
        oResult.SourceQuery = aParams[0];
        oResult.Top = SafeInt(aParams[1]);
        oResult.Left = SafeInt(aParams[2]);
        oResult.AutoPostback = SafeBoolean(aParams[3]);
        oResult.URL = aParams[4];

        //getting the query params into an array is hard... we want to avoid splitting on commas which are escaped
        //with a backslash, but javascript's regex engine doesn't support lookbehind. (I'm reversing the string and using lookahead instead)
        oResult.QueryParams = f.GetValue(oQueryParamsControl).split('').reverse().join('').split(/,(?!\\)/g);
        for (var i = 0; i < oResult.QueryParams.length; i++) oResult.QueryParams[i] = oResult.QueryParams[i].split('').reverse().join('');
        oResult.QueryParams.reverse();

        oResult.ShowHideControls = f.GetValue(oShowHideControlsControl).split(';');

        return oResult;
    }

    this.GetSelectedIndex = function(oTextbox) {
        var oResultsDiv = f.GetObject(oTextbox.id + '_Results');

        for (var i = 0; i < oResultsDiv.childNodes.length; i++) {
            if (f.HasClass(oResultsDiv.childNodes[i], 'selected')) return i;
        }

        return -1;
    }

    this.SaveSelection = function(oTextbox) {
        oTextbox.AC_OldText = f.GetValue(oTextbox);
        oTextbox.AC_OldValue = f.GetValue(oTextbox.id + '_Value');
    }
    this.RestoreOldSelection = function(oTextbox) {
        f.SetValue(oTextbox, oTextbox.AC_OldText);
        f.SetValue(oTextbox.id + '_Value', oTextbox.AC_OldValue);
    }

    this.ClearValue = function(oTextbox) {
        var oParams = AutoComplete.GetParams(oTextbox);
        var oValueControl = f.GetObject(oTextbox.id + '_Value');

        f.SetValue(oValueControl, '0');
    }




    /* Useful Functions */
    this.SetParams = function(oTextbox, sSourceQuery, iTop, iLeft, bAutoPostback) {
        var oParams = AutoComplete.GetParams(f.SafeObject(oTextbox));
        f.SetValue(f.SafeObject(oTextbox).id + '_Params', new Array(sSourceQuery, iTop, iLeft, bAutoPostback, oParams.URL).join('|'));
    }
    this.SetSourceQuery = function(oTextbox, sSourceQuery) {
        var oParams = AutoComplete.GetParams(f.SafeObject(oTextbox));
        AutoComplete.SetParams(oTextbox, sSourceQuery, oParams.Top, oParams.Left, oParams.AutoPostback);
    }

    this.SetQueryParams = function(oTextbox, aQueryParams) {
        var aFinalParams = new Array();

        for (var i = 0; i < aQueryParams.length; i++) {
            aFinalParams[i] = aQueryParams[i].replace(',', '\,');
        }

        f.SetValue(f.SafeObject(oTextbox).id + '_QueryParams', aFinalParams.join(','));
    }
    this.ClearQueryParams = function(oTextbox) {
        AutoComplete.SetQueryParams(oTextbox, new Array());
    }
    this.SetQueryParam = function(oTextbox, iIndex, oValue) {
        var oParams = AutoComplete.GetParams(f.SafeObject(oTextbox));

        oParams.QueryParams[iIndex - 2] = oValue.toString().replace(',', '\,');

        f.SetValue(f.SafeObject(oTextbox).id + '_QueryParams', oParams.QueryParams.join(','));
    }

    this.Clear = function(oTextbox) {
        f.SetValue(oTextbox, '');
        f.SetValue(f.SafeObject(oTextbox).id + '_Value', 0);
    }
    this.GetValue = function(oTextbox) {
        return f.GetValue(f.SafeObject(oTextbox).id + '_Value');
    }

    this.SetValue = function(oTextbox, iValue) {

        if (iValue == 0) {
            AutoComplete.Clear(oTextbox);

        } else {
            var oGetDisplayValueService = new WebService();

            oGetDisplayValueService.Go = function() {
                if (oTextbox.AC_BeforeQuery) oTextbox.AC_BeforeQuery();
                var oParams = AutoComplete.GetParams(oTextbox);

                var aWebServiceParams = new Array(['eSourceQuery', oParams.SourceQuery], ['sQueryParams', oParams.QueryParams.join(',')],
                    ['iValue', iValue]);
                this.RunWebService(oParams.URL, 'http://intuitivesystems', 'GetDisplayValue', aWebServiceParams, this, false);
            }

            oGetDisplayValueService.Done = function(oXMLResponse) {
                var sDisplayValue = this.GetTagValue(oXMLResponse, 'GetDisplayValueResult');

                f.SetValue(oTextbox, sDisplayValue);

                if (sDisplayValue != '') {
                    f.SetValue(f.SafeObject(oTextbox).id + '_Value', iValue);
                } else {
                    f.SetValue(f.SafeObject(oTextbox).id + '_Value', 0);
                }
            }

            oGetDisplayValueService.Go();
        }
    }

}
