﻿/**
* qs.js
* QuerySuggester. 
* Edit the functions: createTooltipDiv, parseSuggestions and buildSuggestionsHtml to change how the suggestions are presented.
*
*/

/**
* Constructor for QuerySuggester.
*/

function QuerySuggester() {
    this.id = querySuggesters.length;
    querySuggesters[this.id] = this;
}

QuerySuggester.prototype.useInlineSuggestion = false;                // Whether or not to display the first suggestion as marked text within the search-box
QuerySuggester.prototype.clearInlineSuggestionBeforeSubmit = false; // Whether or not to remove inline suggestions before submitting

// Public functions

/**
* Initialize the query-suggester.
*
* @param suggestionUrl the url to fetch suggestions from, will append the query string to it
* @param formId        the id of the form that should be submitted if the user clicks a suggestion
* @param tooltipId     the id of the div used to display the suggestsions
*/
QuerySuggester.prototype.initialize = function(suggestionUrl, formId, tooltipId, queryId) {
    this.__enabled = new BackgroundLoader().isEnabled();
    if (this.__enabled) {
        this.__suggestionUrl = suggestionUrl;
        this.__form = this.byId(formId);
        if (!this.__form) {
            this.__enabled = false;
            this.debug("Couldn't find the form, disabling.");
        } else {
            this.setQueryId(queryId);
            if (this.__query) {
                var suggester = this;
                // create the suggest div if does not exist
                suggester.createTooltipDiv(tooltipId);
                //suggester.setDebugAreaId("WebPartWPQ1");

                var oldKeyUp = this.getFunctionBody(this.__query.onkeyup);
                var oldKeyDown = this.getFunctionBody(this.__query.onkeydown);
                this.__query.onkeyup = function(e) { if (typeof e != "undefined") event = e; eval(oldKeyUp); suggester.keyUp(event, queryId); };
                this.__query.onkeydown = function(e) { if (typeof e != "undefined") event = e; eval(oldKeyDown); suggester.keyDown(event, queryId); };

            }
        }
    } else {
        this.debug("Unable to instantiate XMLHttpRequest, disabling.");
    }
};

// Create the div tag where the suggestions html will be placed.
QuerySuggester.prototype.createTooltipDiv = function(tooltipId) {
    // Create suggest div
    main_div = this.byId(tooltipId);
    if (!main_div) {
        main_div = document.createElement("div");
        main_div.id = "autocompleteContainer";
        main_div.style.display = "block";
        query.parentNode.insertBefore(main_div, query.nextSibling)
        query.parentNode.insertBefore(document.createElement("br"), query.nextSibling)
    }
    this.__tooltip = main_div;
};

/**********************************************************************************/
/**** Functions for displaying the suggestions                                 ****/
/**** Edit below to change the way suggestions are presented                   ****/
/**********************************************************************************/

// Parses the result recieved from the matcher server
QuerySuggester.prototype.parseSuggestions = function(suggestions) {
    if (suggestions == "") return;

    var jsonObject = eval('(' + suggestions + ')');

    var queries = new Array();
    var j = 0;
    for (i = 0; i < jsonObject.queries.length; i++) {
        if (jsonObject.queries[i] != "") {
            queries[j] = jsonObject.queries[i];
            j++;
        }
    }
    this.__suggestedQueryCount = queries.length;

    var results = jsonObject.results;
    var titles = new Array();
    var bodies = new Array();
    var urls = new Array();
    for (i = 0; i < results.length; i++) {
        titles[i] = results[i].title;
        bodies[i] = results[i].ifwebdescription;
        urls[i] = results[i].url;
    }

    // validate the parsed matches
    if ((queries.length == 0) && (results.length == 0)) {
        this.debug("failed to parsed matches");
        this.noSuggestions();
        return;
    }

    var parsedMatches = new Array(4);
    parsedMatches[0] = queries;
    parsedMatches[1] = titles;
    parsedMatches[2] = bodies;
    parsedMatches[3] = urls;

    if ((queries.length + results.length) > 2) {
        this.__cache[queries[0]] = parsedMatches;
    }

    this.displaySuggestions(parsedMatches, queries.length + results.length > 2 ? queries[0] : "");
};
QuerySuggester.prototype.displaySuggestions = function(parsedMatches, query) {
    if (((parsedMatches[0].length + parsedMatches[1].length) > 0) && this.__tooltip && this.__query) {
        this.buildSuggestionsHtml(parsedMatches[0], parsedMatches[1], parsedMatches[2], parsedMatches[3]);
        this.show();
        this.showInlineSuggestions(query);
    }
};
/* Build the html tags that will be the content of the tooltip element. */
QuerySuggester.prototype.buildSuggestionsHtml = function(queries, titles, bodies, urls) {
    this.__terms = new Array(queries.length + 1 + titles.length);
    var text = "<div class=\"results\">";

    //queries
    text += "<div id=\"queries\" class=\"queries\" style=\"display:block;\">";
    for (var i = 0; i < queries.length; i++) {
        var term = queries[i];
        // add the match to the cache
        this.__terms[i] = queries[i];

        text += "<div id='tooltip_" + i + "' class=\"suggestionEntry\" onmouseover=\"mouseOver(" + this.id + "," + i + ")\" onmouseout=\"mouseOut(" + this.id + "," + i + ")\" onclick=\"mouseClick(" + this.id + "," + i + ")\"><a>" + term + "</a></div>";
    }
    text += "</div>";

    //results
    text += "<div id=\"summaries\" class=\"summaries\" style=\"display:block;\">";
    for (var j = 0; j < titles.length; j++) {
        var index = j + queries.length;
        // add the match to the cache
        this.__terms[index] = titles[j];

        text += "<div id='tooltip_" + index + "' class=\"suggestionEntry\" onmouseover=\"mouseOver(" + this.id + "," + index + ")\" onmouseout=\"mouseOut(" + this.id + "," + index + ")\">";
        text += "<div class=\"title\"><a id='link_" + index + "' href=\"" + urls[j] + "\" target=\"_blank\">" + titles[j] + "</a></div>";
        text += "<div class=\"body\"><a id='bodylink_" + index + "' href=\"" + urls[j] + "\" target=\"_blank\">" + bodies[j] + "</a></div>";
        text += "</div>";
    }
    text += "</div></div>";

    // set the tooltip inner html
    this.__tooltip.innerHTML = text;
};
QuerySuggester.prototype.toTitleCase = function(str) {
    //lowercase the whole string
    var ls = str.toLowerCase().split(' ');
    //loop through word array
    for (var i = 0; i < ls.length; i++) {
        //replace first letter with uppercase version
        ls[i] = ls[i].charAt(0).toUpperCase() + ls[i].slice(1);
    }
    //rejoin words and return the new string
    return ls.join(' ');
}
QuerySuggester.prototype.showInlineSuggestions = function(query) {
    if (this.__terms.length > 0 && this.__query.value == query && !this.__deletePressed && this.useInlineSuggestion) {
        this.__original = this.__query.value;
        if (this.canHandleRanges()) {
            this.__query.value = this.__terms[0];
            this.selectRange(this.__original.length, this.__query.value.length);
        }
    }
};
QuerySuggester.prototype.updateSuggestions = function(previousIndex, previousMouseIndex) {
    var previous = this.byId("tooltip_" + previousIndex);
    var previousMouse = this.byId("tooltip_" + previousMouseIndex);
    var current = this.byId("tooltip_" + this.__index);
    var currentMouse = this.byId("tooltip_" + this.__mouseIndex);

    if (previous) {
        if (previousIndex != this.__mouseIndex) {
            previous.className = "suggestionEntry";
        }
    }
    if (current) {
        current.className = "suggestionEntryHover";
        if (this.canHandleRanges()) {
            if ((this.__index < this.__suggestedQueryCount) && (this.__index != -1)) {
                this.__query.value = this.__terms[this.__index];
            }
            else {
                this.__selectedSuggestedResult = this.byId("link_" + this.__index);
                this.__query.value = this.__terms[0];
            }
            this.selectRange(this.__original.length, this.__query.value.length);
        }
    }
    if (previousMouse && previousMouseIndex != this.__index) {
        previousMouse.className = "suggestionEntry";
    }
    if (currentMouse) {
        currentMouse.className = "suggestionEntryHover";
    }
};
QuerySuggester.prototype.getXMLDocument = function(xmlString) {
    var xmlDoc = null;
    try //Internet Explorer
    {
        xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
        xmlDoc.async = "false";
        xmlDoc.loadXML(xmlString);
    }
    catch (e) {
        try //Firefox, Mozilla, Opera, etc.
        {
            var parser = new DOMParser();
            xmlDoc = parser.parseFromString(xmlString, "text/xml");
        }
        catch (e) {
            // do nothing
            alert(e.message);
        }
    }
    return xmlDoc;
};



/*** Global mouse-handling functions ***/

/* Set on the suggestions tooltip element */
function mouseOver(id, index) {
    if (id >= 0 && id < querySuggesters.length) {
        querySuggesters[id].mouseOver(index);
    }
}
function mouseOut(id, index) {
    if (id >= 0 && id < querySuggesters.length) {
        querySuggesters[id].mouseOut(index);
    }
}
function mouseClick(id, index) {
    if (id >= 0 && id < querySuggesters.length) {
        querySuggesters[id].mouseClick(index);
    } else if (id == -1 && index == -1) {
        for (var i = 0; i < querySuggesters.length; ++i) {
            querySuggesters[i].mouseClick(index);
        }
    }
}

/* Called when the list of matches returned is empty. Default implementation will simply
hide the list of suggestions. Override for custom behavior. */
QuerySuggester.prototype.noSuggestions = function() {
    this.hide();
};
QuerySuggester.prototype.show = function() {
    if (this.__tooltip && this.__query) {
        this.__tooltip.style.left = this.findPosX(this.__query) + "px";
        this.__tooltip.style.top = (this.findPosY(this.__query) + this.__query.offsetHeight) + "px";
        this.__tooltip.style.width = this.__query.offsetWidth + 100 + "px";
        this.__tooltip.style.visibility = "visible";
    }
};
QuerySuggester.prototype.hide = function() {
    if (this.__tooltip) {
        this.__terms = new Array();
        this.__tooltip.style.visibility = "hidden";
    }
};



/*** Overridable functions ***/

/* Returns the url to visit to obtain suggestions for a query. By default appends the
query to the suggestionUrl member. Override for different behavior. */
/* @param quer the query for which to fetch suggestions */
/* @return the url that will return the suggestions */
QuerySuggester.prototype.getSuggestionUrl = function(query) {
    return this.__suggestionUrl + query;
};
/* Set the id of the textarea where debug-output is written. Initially, there is no such
textarea assigned to the suggester. */
/* @param debugAreaID the id of the textarea to print debug messages to, or boolean false
to disable debugging */
QuerySuggester.prototype.setDebugAreaId = function(debugAreaId) {
    this.__debugAreaId = debugAreaId;
};
/* Set the id of the input-field to do completion for. Not really neccessary to call, as
the id is sent to the key handling methods. */
/* @param queryId the id of the search box */
QuerySuggester.prototype.setQueryId = function(queryId) {
    this.__queryId = queryId;
    this.__query = this.byId(queryId);
};
/* Called when the search form should be submitted (usually because of mouse clik on one
of the suggestions in the list). By default submits the form specified by the formId
parameter of the initialize() function. Override for different behavior. */
QuerySuggester.prototype.submitForm = function() {
    ////S1765FB54_Submit();
    this.__form.submit();
};


/*** Input event handlers ***/
/* Event handler for key up events. */
QuerySuggester.prototype.keyUp = function(event, field) {
    var redirect = false;
    if (!this.__enabled) return;
    else if (field != this.__queryId) this.setQueryId(field);
    if (event) {
        if (this.__timeout) {
            clearTimeout(this.__timeout);
            this.__timeout = false;
        }
        if (event.ctrlKey || event.altKey) {
            return;
        }
        var timeout = 100;
        this.__deletePressed = false;
        switch (event.keyCode) {
            case 9: // Tab
            case 27: // Escape
                this.hide();
                return;
            case 8: // Backspace
            case 46: // Delete
                this.__deletePressed = true;
                timeout = 200;
                break;
            case 13: // Enter
            case 16: // Shift
            case 17: // Ctrl
            case 18: // Alt
            case 20: // Caps Lock
            case 33: // Page up
            case 34: // Page down
            case 35: // End
            case 36: // Home
            case 37: // Arrow left
            case 38: // Arrow up
            case 39: // Arrow right
            case 40: // Arrow down
            case 45: // Insert
                return;
            default:
                timeout = 500;
                break;
        }
        this.__index = -1;
        var qc = this;
        this.__timeout = setTimeout(function() { qc.fetchAndDisplaySuggestions(); }, timeout);
    }
};
/* Some keys also require keyDown handlers, because they never give a key up event. For
instance, pressing the 'tab' key will typically cause focus to leave the search input
box, causing the key up event to be sent to another component. */
QuerySuggester.prototype.keyDown = function(event, field) {
    if (!this.__enabled) return;
    else if (field != this.__queryId) this.setQueryId(field);

    if (event && event.keyCode) {
        switch (event.keyCode) {
            case 9: // Tab
                this.hide();
                break;
            case 13: // Enter
                this.__query.focus();
                if (this.__index == -1) this.clearInlineSuggestion();
                if (this.__index >= this.__suggestedQueryCount) {
                    this.__query.value = this.__terms[0];
                    this.__selectedSuggestedResult.focus();
                }
                break;
            case 38: // Up-arrow
                if (this.__index >= 0) this.updateSuggestions(this.__index--, -2);
                break;
            case 40: // Down-arrow
                if (this.__index < this.__terms.length - 1) this.updateSuggestions(this.__index++, -2);
                break;
            default:
                break;
        }
    }
};



/*** Mouse event handlers ***/

QuerySuggester.prototype.mouseOver = function(index) {
    if (!this.__enabled) return;
    var previous = this.__mouseIndex >= 0 ? this.__mouseIndex : this.__index;
    this.__mouseIndex = index;
    this.updateSuggestions(-2, previous);
};
QuerySuggester.prototype.mouseOut = function(index) {
    if (!this.__enabled) return;
    var previous = this.__mouseIndex;
    this.__mouseIndex = -1;
    this.updateSuggestions(-2, previous);
};
QuerySuggester.prototype.mouseClick = function(index) {
    if (!this.__enabled) return;
    if (index < this.__suggestedQueryCount) {
        if (index >= 0) {
            this.__query.value = this.__terms[index];
            this.submitForm();
        }
        else {
            this.hide();
        }
    }
};



/*** Functions for retrieving suggestions ***/

QuerySuggester.prototype.fetchAndDisplaySuggestions = function() {
    var val = this.__query.value;

    // Set min chars to 3 before we do anything
    // !!!!!!!!!!!!!!!
    if (val.length >= 3) {
        if (this.canHandleRanges()) {
            val = val.substring(0, this.getCaretPosition());
        }
        if (val.length == 0) {
            this.hide();
            this.__prev = val;
            return;
        } else {
            val = val.replace(new RegExp("\\\\", "g"), "\\\\");
        }
        this.__prev = val;
        if (this.__cache[val]) {
            this.debug("cache: " + val);
            this.displaySuggestions(this.__cache[val], this.__prev);
        } else {
            this.debug("query: '" + val + "'");
            this.fetchSuggestion(val);
        }
    }
    else {
        this.hide();
    }
};
QuerySuggester.prototype.fetchSuggestion = function(query) {
    var qc = this;
    var bl = new BackgroundLoader();
    this.debug("fetching: '" + query + "'");
    bl.setLoadedCallback(function(content) { qc.parseSuggestions(content); });
    bl.setErrorCallback(function(error) { qc.debug("Couldn't get suggestions:\n" + error); });
    bl.loadUrl(this.getSuggestionUrl(query));
};



/*** Text-selection helper-functions ***/

QuerySuggester.prototype.canHandleRanges = function() {
    return this.__query.createTextRange || this.__query.setSelectionRange;
};
QuerySuggester.prototype.selectRange = function(from, to) {
    if (this.__query.createTextRange) {
        var t = this.__query.createTextRange();
        t.moveStart("character", to);
        t.select();
    } else if (this.__query.setSelectionRange) {
        this.__query.setSelectionRange(from, to);
    } else {
        this.debug("Couldn't select range.");
    }
};
QuerySuggester.prototype.getCaretPosition = function() {
    if (document.selection) {
        var range = document.selection.createRange().duplicate();
        range.collapse(true);
        range.moveStart("character", -1000);
        return range.text.length;
    } else if (this.__query.setSelectionRange) {
        return this.__query.selectionStart;
    } else {
        this.debug("Couldn't find caret position.");
        return this.__query.value.length;
    }
};
QuerySuggester.prototype.clearInlineSuggestion = function() {
    if (this.__query && this.canHandleRanges() && this.clearInlineSuggestionBeforeSubmit) {
        this.__query.value = this.__query.value.substring(0, this.getCaretPosition());
    }
};



/*** General helper-functions ***/

/* Helper-function that extracts the body of a function, by converting it to a string, and
extracting the substring from after the first { to the last }. If the func argument
isn't a function, an empty string is returned. */
/* @param func the function whose body to extract */
/* @return the body of func */
QuerySuggester.prototype.getFunctionBody = function(func) {
    var body = "";
    if (typeof func == "function") {
        body = func.toString();
        body = body.substring(body.indexOf("{") + 1, body.lastIndexOf("}"));
    }
    return body;
};
QuerySuggester.prototype.findPosX = function(obj) {
    var curleft = 0;
    if (obj.offsetParent) {
        while (obj.offsetParent) {
            curleft += obj.offsetLeft;
            obj = obj.offsetParent;
        }
    } else if (obj.x)
        curleft += obj.x;
    return curleft;
};
QuerySuggester.prototype.findPosY = function(obj) {
    var curtop = 0;
    if (obj.offsetParent) {
        while (obj.offsetParent) {
            curtop += obj.offsetTop
            obj = obj.offsetParent;
        }
    } else if (obj.y) {
        curtop += obj.y;
    }
    return curtop;
};
QuerySuggester.prototype.byId = function(id) {
    var element = document.getElementById ? document.getElementById(id) : false;
    return element && element != null ? element : false;
};
QuerySuggester.prototype.debug = function(message) {
    if (this.__debugAreaId) {
        var err = this.byId(this.__debugAreaId);
        if (err) {
            err.innerText += message + "; ";
        }
    }
};



/*** Private members ***/

QuerySuggester.prototype.__suggestionUrl = false;   // The url to fetch suggestions from
QuerySuggester.prototype.__form = false;            // The form
QuerySuggester.prototype.__query = false;           // The query input field
QuerySuggester.prototype.__queryId = false;         // The id of the query input field
QuerySuggester.prototype.__tooltip = false;         // The tooltip div
QuerySuggester.prototype.__debugAreaId = false;     // Text-area for debugging

QuerySuggester.prototype.__timeout = false;         // The current timeout
QuerySuggester.prototype.__prev = "";               // The previously sent term
QuerySuggester.prototype.__original = "";           // The term before panning into the suggestions
QuerySuggester.prototype.__index = -1;              // The index of the text cursor in the list of suggestions
QuerySuggester.prototype.__mouseIndex = -1;         // The position of the mouse in the list of suggestions
QuerySuggester.prototype.__terms = new Array();     // The list of suggestions
QuerySuggester.prototype.__cache = new Array();     // A result-cache
QuerySuggester.prototype.__enabled = false;
QuerySuggester.prototype.__deletePressed = false;   // Delete or backspace has been pressed
QuerySuggester.prototype.__suggestedQueryCount = 0; // The count of suggested queries in suggestions
QuerySuggester.prototype.__selectedSuggestedResult = "";    // The selected link of suggested result

// Global array of all instantiated query-suggesters
var querySuggesters = new Array();
