/**
@fileOverview
Part of the QlikView WorkBench Javascript Library (http://www.qlikwebworkbench.com)<br /><br />
Copyright (c) 2009 QlikTech International AB (http://www.qliktech.com)<br /><br />
All Rights Reserved. Email support@qliktech.com for licensing and support information.<br /><br />
@author <a href="mailto:email@industrialcodebox.com">Industrial CodeBox</a>
*/

Qww.Ctls.GlobalSearch = {};

/** 
Constructs a new Qww.Ctls.GlobalSearch.Mgr instance.

@class JavaScript control to search across multiple fields (listboxes) within an application. Note that 
you should create copies of the listboxes you wish to use which are utilised only by this control. For example if you are 
already showing LBCOUNTRY as a listbox control on your page, create a copy of this listbox named LBCOUNTRY_SEARCH and use this 
in conjunction with the Qww.Ctls.GlobalSearch.Mgr control.<br />

@param {Object} cfg JSON object to configure Qww.Ctls.GlobalSearch.Mgr.

@param {String} cfg.ResultsElementId Element to show search results in.

@param {String} cfg.SearchTextBoxId ID of text box where search term is entered.

@param {Qww.Ctls.GlobalSearch.Field[]} cfg.Fields Array of fields to search in the QlikView document.

@param {String} [cfg.Theme=Default] Theme to use (this string gets used in css class names, for example QwwJs_GlobalSearch-NoMatchesFound-ThemeName).

@param {Bool} [cfg.AutomaticallyClearSearchString=false] If set to true the search box is cleared whenever results are selected.

@param {Bool} [cfg.CloseSearch=false] If set to true then each time the search command is sent to QlikView Server 
the search will also be 'closed' meaning that the matching listbox items will be selected (and any other QlikView objects 
will be updated accordingly).

@param {Integer} [cfg.SearchInterval=500] Rather than sending new search requests as soon as the user starts typing, the search control 
waits this amount of milliseconds after the last character was typed then sends the search.

@param {Function} [cfg.OnRenderResultsSection] Function to run to render custom results. Function will have the following 
signature: function(results, lbId, lbTitle, lbMatchString, lbNoMatchString, maxLengthOfMatchStringToShow, 
maxNumberOfResultsToShow, lbSelectAllText, lbReduceResultstext).

@param {Function} [cfg.OnInitialised] Function to call when the control has been initialised.

@param {Function} [cfg.OnRenderBegin] Function to call when the control is about to re-render.

@param {Function} [cfg.OnRenderComplete] Function to call when the control is about to complete rendering.

@param {Function} [cfg.OnBeforeCloseSearch] Function to call before the search results close. Should be 
of the form function(searchCtl, isFromCloseButton). The isFromCloseButton is false if the search is closing for 
a reason other than the [X] was clicked, for example when the user clears the search box text the search closes. 
If you return false from this function the closing of the search will be cancelled.

@param {Function} [cfg.OnSearchClosed] Function to call after the search results close. Should be 
of the form function(searchCtl, isFromCloseButton). The isFromCloseButton is false if the search is closing for 
a reason other than the [X] was clicked, for example when the user clears the search box text the search closes. 

@param {Function} [cfg.OnGetSearchString|function(str){return "*" + str + "*";}] Function to call to return the search 
string to be sent to QlikView Server. Defaults to a 'standard' *search_string* pattern. NOTE this is only called when the 
str.length > 0, otherwise '***' will be sent as the search string to clear the search.

@param {Function} [cfg.OnSearchInitiated] Function to call when the search initiates. Useful, for example, to 
show an animated spinner to give the user immediate feedback that a search is in progress.

@param {Function} [cfg.OnSearchCompleted] Function to call after the search has completed. Useful, for example, to 
hide an animated spinner which has been shown on the OnSearchInitiated event.

@param {Function} [me.Cfg.OnSearchPerformed] Function to call when a search is run against the server. The function 
should accept the parameters (Qww.Ctls.GlobalSearch.Mgr, searchString).

@example
var objQvExSearchCtl1 = new Qww.Ctls.GlobalSearch.Mgr(
{
"Fields":[
new Qww.Ctls.GlobalSearch.Field(
{
ObjectID: "LBSEARCHACTORS", 
Title: "Actors", 
MatchText: "Were you looking for the following actors:", 
NoMatchText: "No actors were found. Please refine your search", 
MaxNumberOfLettersToShowInResult: 5,
MaxNumberOfResultsToShow: 7, 
SelectAllText: "Select Them All!",
ReduceResultsText: "Change search text to reduce the results..."
}),
new Qww.Ctls.GlobalSearch.Field(
{
ObjectID: "LBSEARCHDIRECTOR", 
Title: "Directors",
MatchText: "Were you looking for the following directors:", 
NoMatchText: "No directors were found. Please refine your search",
MaxNumberOfLettersToShowInResult: -1,
MaxNumberOfResultsToShow:  -1, 
SelectAllText: "[Select All]",
ReduceResultsText: "lengthen search text to reduce results further..."
})
],
"ResultsElementId" : "QvExSearchCtl1_results",
"SearchTextBoxId" : "QvExSearchCtl1_txtSearch",
"Theme" : "",
"AutomaticallyClearSearchString" : true
});

&lt;div id="ctl00_cphContent_QvExSearchCtl1" class="QwwJs_GlobalSearch-OuterDiv" 
style="display:inline-block;width:150px;"&gt;
&lt;input type="text" id="QvExSearchCtl1_txtSearch" 
class="QwwJs_GlobalSearch-TextBox" style="width:100%;" /&gt;
&lt;div class="QwwJs_GlobalSearch-SearchResults" 
id="QvExSearchCtl1_results" style="z-index:100;position:absolute;display:none;"&gt;
&lt;/div&gt;
&lt;/div&gt;

@returns {Qww.Ctls.GlobalSearch.Mgr} Qww.Ctls.GlobalSearch.Mgr
*/
Qww.Ctls.GlobalSearch.Mgr = function(cfg) {
    var me = this;

    /**
    Reference to the configuration object which was used to create construct the object. This 
    allows certain properties to be updated after initial instantiation.
    @type Object
    */
    this.Cfg = cfg;

    performEmptySearchOnClosing = false;

    this.ClassPrefix = "QwwJs_GlobalSearch";

    if (!this.Cfg.Theme)
        this.Cfg.Theme = "Default";

    var applicationId = cfg.ApplicationID;

    var currentSearch = 0;
    var inSearch = false;
    var str = "";
    var result_text = "";
    var resultsElement;
    var searchBox;

    if (cfg.CloseSearch == null)
        cfg.CloseSearch = false;

    var searchInterval = (cfg.SearchInterval == null) ? 500 : cfg.SearchInterval;

    var automaticallyClearSearchString = false;

    if (this.Cfg.AutomaticallyClearSearchString)
        automaticallyClearSearchString = this.Cfg.AutomaticallyClearSearchString;

    function t(msg) {
        qwwHub.Trace("Qww.Ctls.GlobalSearch.Mgr:" + msg);
    };

    var _lastSearchText;

    function performSearch() {
        inSearch = true;

        if (searchBox.value == _lastSearchText) {
            if (cfg.OnSearchCompleted)
                cfg.OnSearchCompleted(this);
        }
        else {
            me.PerformSearch(searchBox.value);
            _lastSearchText = searchBox.value;
        }
    }

    var _delayedTimerId;

    this.OnDocumentLoaded = function() {
        if (me.Cfg.SearchTextBoxId) {
            searchBox = document.getElementById(this.Cfg.SearchTextBoxId);

            lastText = searchBox.value;

            //
            // Make sure search is cleared when first used.
            //
            searchBox.onclick = function(oEvent) {
                if (this.isFirst == null) {
                    this.value = "";
                    this.isFirst = false;
                }
            };

            searchBox.onkeyup = function(oEvent) {

                if (cfg.OnSearchInitiated)
                    cfg.OnSearchInitiated(this);

                if (_delayedTimerId != null) window.clearTimeout(_delayedTimerId);

                _delayedTimerId = setTimeout(performSearch, searchInterval);

                // Fix for issue where search propogates through to listboxes too.
                if (!oEvent)
                    oEvent = window.event;

                oEvent.cancelBubble = true;
                return false;
                // Fix for issue where search propogates through to listboxes too.
            };
        }

        if (cfg.ResultsElementId) {
            resultsElement = document.getElementById(cfg.ResultsElementId);
            showResults(false);
        }

        if (me.Cfg.OnRenderResultsSection == null)
            me.Cfg.OnRenderResultsSection = onRenderResultsSectionDefault;

        if (me.Cfg.OnRenderResultsHeader == null)
            me.Cfg.OnRenderResultsHeader = onRenderResultsHeaderDefault;

        searchBox.CloseSearch = function(isFromCloseButton) {

            if (performEmptySearchOnClosing == null) performEmptySearchOnClosing = true;

            if (isFromCloseButton == null) isFromCloseButton = false;

            if (cfg.OnBeforeCloseSearch)
                if (cfg.OnBeforeCloseSearch(this, isFromCloseButton) == false)
                return;

            // me.PerformSearch("*"); CB 240409 - Not sure why we needed this but it was causing the
            // selection to fail as the '*' search was running at the end 
            // of any selections.

            //abort();

            if (automaticallyClearSearchString == true)
                this.value = "";

            showResults(false);

            if (cfg.OnSearchClosed)
                cfg.OnSearchClosed(this, isFromCloseButton);

            if (cfg.OnSearchCompleted)
                cfg.OnSearchCompleted(this);
        };

        searchBox.MakeSelection = function(id, value) {
            Qww.ListBox.Mgr.MakeSingleSelection(applicationId, id, value);

            //abort();

            //this.CloseSearch();
        };
    };

    /**
    Sets the text in the search box.
    @param {String} txt Text to set.
    */
    this.SetSearchText = function(txt) {
        searchBox.value = txt;
    };

    /**
    Closes the results panel.
    */
    this.CloseSearch = function() {
        searchBox.CloseSearch();
    };

    var haveListBoxesBeenRegistered = false;

    //    function abort() {
    //        for (i = 0; i < cfg.Fields.length; i++) {
    //            
    //            val = cfg.Fields[i];

    //            var objectId = val.ObjectID;

    //            var isFinal = (i == cfg.Fields.length - 1);

    //            qwwHub.DoAvqSet(applicationId, objectId, "closesearch", "abort", isFinal);
    //        }
    //    }

    //    /**
    //    @ignore
    //    Returns an array of Qww.QvsConnectorSet objects which represents the initiasation which 
    //    needs to be sent to QlikView server in order to initialise this object.
    //    @returns {Qww.QvsConnectorSet[]} Array of Qww.QvsConnectorSet objects which represents 
    //    the initiasation for this object.
    //    */
    //    this.GetInitialisationSets = function() {

    //        var sets = [];

    //        var arrLength = me.Cfg.Fields.length;

    //        for (i = 0; i < arrLength; i++) {

    //            val = me.Cfg.Fields[i];

    //            var maxNoResults = val.MaxNumberOfResultsToShow;

    //            if (!maxNoResults)
    //                maxNoResults = 20;

    //            //addListBox(val.ObjectID, maxNoResults);

    //            //qwwHub.Register(new Qww.ListBox.Mgr({ ObjectID: val.ObjectID, PageSize: maxNoResults }));

    //            sets.push(new Qww.QvsConnectorSet(applicationId, val.ObjectID, "add", "mode;value;pageoffset;pagesize;totalsize", false));
    //            sets.push(new Qww.QvsConnectorSet(applicationId, val.ObjectID + ".C0", "add", "mode;text;", false));
    //            sets.push(new Qww.QvsConnectorSet(applicationId, val.ObjectID, "pagesize", maxNoResults, false));
    //            sets.push(new Qww.QvsConnectorSet(applicationId, val.ObjectID, "pageoffset", "0", (i == me.Cfg.Fields.length - 1)));
    //        }

    //        haveListBoxesBeenRegistered = true;

    //        return sets;
    //    };

    for (i = 0; i < me.Cfg.Fields.length; i++) {

        val = me.Cfg.Fields[i];

        var maxNoResults = val.MaxNumberOfResultsToShow;

        if (!maxNoResults)
            maxNoResults = 20;

        qwwHub.Register(
            new Qww.ListBox.Mgr(
            {
                ObjectID: val.ObjectID,
                PageSize: maxNoResults
            })
        );
    }

    this.OnAllAvqUpdateCompletesCalled = function() {

        //debugger;

        //        if (!haveListBoxesBeenRegistered) {

        //            qwwHub.DoAllSets(me.GetInitialisationSets());

        //            //            if(searchBox)
        //            //                searchBox.disabled = false;

        //            haveListBoxesBeenRegistered = true;
        //        }

        if (inSearch == true) {

            search();

            var arrLength = me.Cfg.Fields.length;

            for (var i = 0; i < arrLength; i++) {

                var field = me.Cfg.Fields[i];

                var lbId = field.ObjectID;

                var results = getResults(lbId);

                //if (results != null) 
                {
                    if (me.Cfg.OnRenderResultsSection) {

                        var lbTitle = field.Title;
                        var lbMatchString = field.MatchText;
                        var lbNoMatchString = field.NoMatchText;
                        var maxLengthOfMatchStringToShow = field.MaxNumberOfLettersToShowInResult;

                        if (maxLengthOfMatchStringToShow <= 0)
                            maxLengthOfMatchStringToShow = null;

                        var maxNumberOfResultsToShow = field.MaxNumberOfResultsToShow;

                        if (maxNumberOfResultsToShow <= 0)
                            maxNumberOfResultsToShow = null;

                        var lbSelectAllText = field.SelectAllText;
                        var lbReduceResultstext = field.ReduceResultsText;

                        result_text += me.Cfg.OnRenderResultsSection(results, lbId, lbTitle, lbMatchString, lbNoMatchString, maxLengthOfMatchStringToShow, maxNumberOfResultsToShow, lbSelectAllText, lbReduceResultstext);
                    }
                    else {
                        result_text += "No OnRenderResultsSection function registered.<br />";
                    }
                }

            };

            if (me.Cfg.OnRenderResultsHeader)
                var headerHtml = me.Cfg.OnRenderResultsHeader();

            resultsElement.innerHTML = headerHtml + result_text;

            var arrLength = me.Cfg.Fields.length;

            for (var i = 0; i < arrLength; i++) {

                var lbId = me.Cfg.Fields[i].ObjectID;
                var element = document.getElementById(lbId + "_results");

                if (element)
                    Qww.Ctls.GlobalSearch.Mgr.HighlightWord(element, str, me.GetClassNameWithThemePostfix("Searchword"));
            }

        }

        if (cfg.OnSearchCompleted)
            cfg.OnSearchCompleted(this);

    };

    this.PerformSearch = function(searchString) {

        t("Searching '" + searchString + "'");

        if (searchString == '') {
            searchString = "*";
        }

        result_text = "";

        if (searchString == str)
            return;
        else
            str = searchString;

        if (str.length == 0) {
            showResults(false);
            search();
        }
        else {
            inSearch = true;
            showResults(true);
            str = searchString;

            inSearch = true;

            search();

            if (searchString == "*")
                searchBox.CloseSearch();
        }
    };

    // START PRIVATE INTERFACE ---------------------------------------------------
    //

    function getCloseSearchJsFunction(isFromCloseButton) {

        if (isFromCloseButton == null) isFromCloseButton = true;

        if (isFromCloseButton == true)
            return "document.getElementById(\"" + cfg.SearchTextBoxId + "\").CloseSearch(true);";
        else
            return "document.getElementById(\"" + cfg.SearchTextBoxId + "\").CloseSearch(false);";
    };

    function onRenderResultsHeaderDefault() {
        return "<span class='" + me.GetClassNameWithThemePostfix("CloseSearchIcon") + "' onclick='" + getCloseSearchJsFunction() + "' style='cursor:pointer;float:right'>[X]</span>";
    };

    function getMakeMultipleSelectionMethodName() {
        return "Qww.ListBox.Mgr.MakeSelection";
    };

    function getMakeSelectionMethodName() {
        return "Qww.ListBox.Mgr.MakeSingleSelection";
        //return "document.getElementById(\"" + cfg.SearchTextBoxId + "\").MakeSelection";
        //return "document.MakeSelection";
    };

    // Default rendering method
    function onRenderResultsSectionDefault(results, id, title, matchString, noMatchString, maxLettersPerResult, maxNumberOfResultsToShow, selectAllText, reduceResultsText) {

        if (results == null) return '';

        var html = "<div style='padding:4px 4px 4px 4px;'>";

        if (results == null || results.length == 0) {
            if (noMatchString)/// <reference path="QwwJs_GlobalSearchMgr.js" />

                html += "<span class='" + me.GetClassNameWithThemePostfix("NoMatchesFound") + "'>" + noMatchString + "</span>";
            else
                html += "<span class='" + me.GetClassNameWithThemePostfix("NoMatchesFound") + "'>" + title + "s</span>";
        }
        else {
            if (matchString)
                html += "<span class='" + me.GetClassNameWithThemePostfix("WereYouLookingFor") + "'>" + matchString + "</span>";
            else
                html += "<span class='" + me.GetClassNameWithThemePostfix("WereYouLookingFor") + "'>Were you looking for " + title + "s matching:</span>";

            html += "<br />";

            if (results.length > 1) {

                var arrayOfItems = [];

                var arrLength = results.length;

                for (i = 0; i < arrLength; i++) {

                    arrayOfItems.push(results[i].Value);

                    //                    if (i < arrLength - 1) {
                    //                        arrayOfItems += ",";
                    //                    }
                }

                arrayOfItems = "[" + arrayOfItems.join(',') + "]";

                //var selectAllFunction = "Qww.ListBox.Mgr.MakeSelection(\"" + id + "\"," + arrayOfItems + ");";
                var selectAllFunction = getMakeMultipleSelectionMethodName() + "(\"" + applicationId + "\", \"" + id + "\"," + arrayOfItems + ");";
                //selectAllFunction += getCloseSearchJsFunction();

                selectAllFunction = getCloseSearchJsFunction(false) + selectAllFunction;

                if (selectAllText == null)
                    selectAllText = "[Select All]";

                html += " <span onclick='" + selectAllFunction + "' class='" + me.GetClassNameWithThemePostfix("SelectAll") + "'>" + selectAllText + "</span><br />";

            }

            var resultsId = id + "_results";

            html += "<span id='" + resultsId + "'>";

            var wasMaxResultsExceeded = false;

            var arrLength = results.length;

            for (i = 0; i < arrLength; i++) {

                //if(i > cfg.MaxNumberOfResultsPerField - 1){

                if (maxNumberOfResultsToShow != null && i > maxNumberOfResultsToShow - 1) {
                    wasMaxResultsExceeded = true;
                    break;
                }
                else {
                    if (applicationId == null) applicationId = "null";

                    //var selectFunction = "Qww.ListBox.Mgr.MakeSingleSelection(\"" + id + "\"," + results[i].Value + ");";
                    var selectFunction = getMakeSelectionMethodName() + "(\"" + applicationId + "\",\"" + id + "\"," + results[i].Value + ");";
                    //alert(selectFunction);
                    //selectFunction += getCloseSearchJsFunction();
                    selectFunction = getCloseSearchJsFunction() + selectFunction;

                    var text = results[i].Text;

                    if (maxLettersPerResult != null && maxLettersPerResult != '' && maxLettersPerResult != -1 && text.length > maxLettersPerResult)
                        text = text.substring(0, maxLettersPerResult) + "...";


                    var singleMatchContents = text;

                    if (me.Cfg.OnRenderSingleMatch)
                        singleMatchContents = me.Cfg.OnRenderSingleMatch(text);

                    if (id == "LB_NAME_WEB") {
                        html += "<span class='" + me.GetClassNameWithThemePostfix("MatchedItemProduct") + "' onclick='" + selectFunction + "' style='cursor:pointer'>" + singleMatchContents + ",</span> ";
                    }
                    else {
                        html += "<div class='" + me.GetClassNameWithThemePostfix("MatchedItem") + "' onclick='" + selectFunction + "' style='cursor:pointer'>" + singleMatchContents + "</div>";
                    }
                }
            }

            html += "</span>";

            if (wasMaxResultsExceeded == true)// && reduceResultsText != '')
                html += "<span class='" + me.GetClassNameWithThemePostfix("TruncatedResults") + "'>" + reduceResultsText + "</span>";
            //html += "<span class='" + me.GetClassNameWithThemePostfix("TruncatedResults") + "'>lengthen search text to reduce results further ...</span>";
        }

        return html + "</div>";
    };

    function showResults(show) {
        // Fading not working
        //var obj = $(this).find('#' + cfg.ResultsElementId);

        if (show == true) {
            //resultsElement.style.display = ''
            $(resultsElement).fadeIn(100);
        }
        else {
            $(resultsElement).fadeOut(100);
            //resultsElement.style.display = 'none'
        }
    };

    //    function highlightWord(node, word) {
    //        // Iterate into this nodes childNodes
    //        if (node.hasChildNodes) {
    //            var hi_cn;

    //            var arrLength = node.childNodes.length;

    //            for (hi_cn = 0; hi_cn < arrLength; hi_cn++) {
    //                highlightWord(node.childNodes[hi_cn], word);
    //            }
    //        }

    //        // And do this node itself
    //        if (node.nodeType == 3) { // text node
    //            tempNodeVal = node.nodeValue.toLowerCase();
    //            tempWordVal = word.toLowerCase();
    //            if (tempNodeVal.indexOf(tempWordVal) != -1) {
    //                pn = node.parentNode;
    //                // check if we're inside a "nosearchhi" zone
    //                checkn = pn;
    //                while (checkn.nodeType != 9 &&
    //			    checkn.nodeName.toLowerCase() != 'body') {
    //                    // 9 = top of doc
    //                    if (checkn.className.match(/\bnosearchhi\b/)) { return; }
    //                    checkn = checkn.parentNode;
    //                }
    //                if (pn.className != me.GetClassNameWithThemePostfix("Searchword")) {
    //                    // word has not already been highlighted!
    //                    nv = node.nodeValue;
    //                    ni = tempNodeVal.indexOf(tempWordVal);
    //                    // Create a load of replacement nodes
    //                    before = document.createTextNode(nv.substr(0, ni));
    //                    docWordVal = nv.substr(ni, word.length);
    //                    after = document.createTextNode(nv.substr(ni + word.length));
    //                    hiwordtext = document.createTextNode(docWordVal);
    //                    hiword = document.createElement("span");
    //                    hiword.className = me.GetClassNameWithThemePostfix("Searchword");
    //                    hiword.appendChild(hiwordtext);
    //                    pn.insertBefore(before, node);
    //                    pn.insertBefore(hiword, node);
    //                    pn.insertBefore(after, node);
    //                    pn.removeChild(node);
    //                }
    //            }
    //        }
    //    };

    //    function addListBox(id, maxNoResults) {
    //        if (qwwHub.DoAvqSelect(applicationId, id) == null) {
    //            qwwHub.DoAvqSet(applicationId, id, "add", "mode;value;pageoffset;pagesize;totalsize", false);
    //            qwwHub.DoAvqSet(applicationId, id + ".C0", "add", "mode;text;", false);
    //            qwwHub.DoAvqSet(applicationId, id, "pagesize", maxNoResults, false);
    //            qwwHub.DoAvqSet(applicationId, id, "pageoffset", "0", false);
    //        }
    //    };

    function getResults(id) {

        var result = qwwHub.DoAvqSelect(applicationId, id);

        if (result != null) {


            //var vals = $("value", result);
            var vals = $("value[name=C0]", result);

            if (vals.length == 0)
                return null;

            var QVListboxValues = vals[0].childNodes;

            var results = new Array();
            var currentIndex = 0;

            var arrLength = QVListboxValues.length;

            for (var i = 0; i < arrLength; i++) {

                var newItem = Qww.ListBox.Mgr.GetListItemFromNode(QVListboxValues[i]);

                if (newItem.Value > -1) {

                    if (cfg.CloseSearch == true && newItem.State == Qww.ListBox.Mgr.ItemState.Selected) {
                        results[currentIndex++] = newItem;
                    }
                    else {
                        results[currentIndex++] = newItem;
                    }

                }
            }

            return results;
        }

        return null;
    };


    function search() {
        if (currentSearch < cfg.Fields.length) {
            var lbId = cfg.Fields[currentSearch].ObjectID;
            //lbId = getFullID(lbId);

            //var search = "*" + str + "*";

            if (str.length == 0)
                var search = "***";
            else {
                if (cfg.OnGetSearchString)
                    var search = cfg.OnGetSearchString(str);
                else
                    var search = "*" + str + "*";
            }

            qwwHub.DoAvqSet(applicationId, lbId, "pageoffset", "0", false);

            if (cfg.CloseSearch == false)
                qwwHub.DoAvqSet(applicationId, lbId, "search", search, true);
            else {
                qwwHub.DoAvqSet(applicationId, lbId, "search", search, true);

                setTimeout(function() {
                    qwwHub.DoAvqSet(applicationId, lbId, "closesearch", "accept", true);
                }, 200);
            }

            currentSearch++;

            if (currentSearch == 1 && search != "***") {

                qwwHub.TrackAction(applicationId, "Search", search);

                if (me.Cfg.OnSearchPerformed)
                    me.Cfg.OnSearchPerformed(this, search);
            }
        }
        else {
            inSearch = false;
            currentSearch = 0;
        }
    };
    //
    // END PRIVATE INTERFACE ---------------------------------------------------

    qwwHub.Register(this);

    searchBox = document.getElementById(this.Cfg.SearchTextBoxId);

    if (this.Cfg.OnInitialised)
        this.Cfg.OnInitialised(me);
};

Qww.Ctls.GlobalSearch.Mgr.prototype = new Qww.ControlBase();

Qww.Ctls.GlobalSearch.Mgr.HighlightWord = function(node, word, classToHighlightMatches) {
    // Iterate into this nodes childNodes
    if (node.hasChildNodes) {
        var hi_cn;

        var arrLength = node.childNodes.length;

        for (hi_cn = 0; hi_cn < arrLength; hi_cn++) {
            Qww.Ctls.GlobalSearch.Mgr.HighlightWord(node.childNodes[hi_cn], word);
        }
    }

    // And do this node itself
    if (node.nodeType == 3) { // text node
        tempNodeVal = node.nodeValue.toLowerCase();
        tempWordVal = word.toLowerCase();
        if (tempNodeVal.indexOf(tempWordVal) != -1) {
            pn = node.parentNode;
            // check if we're inside a "nosearchhi" zone
            checkn = pn;
            while (checkn.nodeType != 9 &&
			    checkn.nodeName.toLowerCase() != 'body') {
                // 9 = top of doc
                if (checkn.className.match(/\bnosearchhi\b/)) { return; }
                checkn = checkn.parentNode;
            }
            if (pn.className != classToHighlightMatches) {
                // word has not already been highlighted!
                nv = node.nodeValue;
                ni = tempNodeVal.indexOf(tempWordVal);
                // Create a load of replacement nodes
                before = document.createTextNode(nv.substr(0, ni));
                docWordVal = nv.substr(ni, word.length);
                after = document.createTextNode(nv.substr(ni + word.length));
                hiwordtext = document.createTextNode(docWordVal);
                hiword = document.createElement("span");
                hiword.className = classToHighlightMatches;
                hiword.appendChild(hiwordtext);
                pn.insertBefore(before, node);
                pn.insertBefore(hiword, node);
                pn.insertBefore(after, node);
                pn.removeChild(node);
            }
        }
    }
};

/**
Constructs a new Qww.Ctls.GlobalSearch.Field instance.
@class Javscript object to represent a field (listbox) which should be searched by the {@link Qww.Ctls.GlobalSearch.Mgr}.<br />
@param {Object} field JSON Object containing information on field.
@param {String} field.ObjectID Id of listbox object in QlikView document to search. Note that it is recommended you create specific copied/instances 
@param {String} [field.Title=field.ObjectID] Title string to use in the search results list above the list of matching entries for this field.
@param {String} [field.MatchText="Were you looking for the following?"] String to use above the list of matching entries for this field.
@param {String} [field.NoMatchText="No Matches found."] String to use when no matches are found against a particular field.
@param {Int} [field.MaxNumberOfLettersToShowInResult=-1] Maximum number of letters to show in a result entry.
@param {Int} [field.MaxNumberOfResultsToShow=10] Maximum number of results to show for the field.
@param {String} [field.SelectAllText="[Select All]"] Text to show for the select all results for this field link.
@param {String} [field.ReduceResultsText="lengthen search text to reduce results further..."] Text to show when more results are available than shown.
*/
Qww.Ctls.GlobalSearch.Field = function(field) {

    /** 
    Id of listbox object in QlikView document to search. Note that it is recommended you create specific copied/instances 
    @type String
    */
    this.ObjectID = field.ObjectID;

    /** 
    Title string to use in the search results list above the list of matching entries for this field.
    @type String
    */
    this.Title = (field.Title) ? field.Title : this.ObjectID;

    /** 
    String to use above the list of matching entries for this field.
    @type String
    */
    this.MatchText = (field.MatchText) ? field.MatchText : "Were you looking for the following?";

    /** 
    String to use when no matches are found against a particular field.
    @type String
    */
    this.NoMatchText = (field.NoMatchText) ? field.NoMatchText : "No Matches found.";

    /** 
    Maximum number of letters to show in a result entry.
    @type Int
    */
    this.MaxNumberOfLettersToShowInResult = (field.MaxNumberOfLettersToShowInResult != null) ? field.MaxNumberOfLettersToShowInResult : -1;

    /** 
    Maximum number of results to show for the field.
    @type Int
    */
    this.MaxNumberOfResultsToShow = (field.MaxNumberOfResultsToShow != null && field.MaxNumberOfResultsToShow != -1) ? field.MaxNumberOfResultsToShow : 10;

    /** 
    Text to show for the select all results for this field link.
    @type String
    */
    this.SelectAllText = (field.SelectAllText) ? field.SelectAllText : "[Select All]";

    /** 
    Text to show when more results are available than shown.
    @type String
    */
    this.ReduceResultsText = (field.ReduceResultsText) ? field.ReduceResultsText : "lengthen search text to reduce results further...";
};

Qww.Ctls.GlobalSearch.Field.FromArgs = function(objectID, title, matchText, noMatchText, maxNumberOfLettersToShowInResult, maxNumberOfResultsToShow, selectAllText, reduceResultsText) {

    var field = {};

    field.ObjectID = objectID;

    field.Title = title;
    field.MatchText = matchText;
    field.NoMatchText = noMatchText;
    field.MaxNumberOfLettersToShowInResult = maxNumberOfLettersToShowInResult;
    field.MaxNumberOfResultsToShow = maxNumberOfResultsToShow;
    field.SelectAllText = selectAllText;
    field.ReduceResultsText = reduceResultsText;

    return new Qww.Ctls.GlobalSearch.Field(field);
};

