﻿/*
    script object that reads text from an input and makes ajax requests for a list of suggested values
        - binding is done in script
        - all DOM elements are added in script (thus when script is disabled this has a minimal footprint)
        - URL of the ajax page is passed in (a list of strings is expected as the return value)
*/

var c_key_leftArrow = 37;
var c_key_upArrow = 38;
var c_key_rightArrow = 39;
var c_key_downArrow = 40;

var c_key_tab = 9;

var c_key_enter = 13;
var c_key_escape = 27;

// ------------------------------------------------------------

// data object used to reference values and DOM elements quickly
function TypeAheadValue(value, LI, anchor){
    this.m_value  = value;
    this.m_LI     = LI;
    this.m_anchor = anchor;
}

// ------------------------------------------------------------

// constructor
function TypeAhead(inputElementId, lookupUrl, lookupValueParamName, submitInputElementId, searchBoxUniqueId){
    this.m_input = document.getElementById(inputElementId);
    this.m_lookupUrl = lookupUrl;
    this.m_lookupValueParamName = lookupValueParamName;
    
    // optional element ID, used to ensure that the right submit is used to preserve .Nets event handling model
    //                      if not supplied the fallback is to use this.m_input.form.submit(e);
    this.m_submitInput = null;
    if(submitInputElementId != null){

        this.m_submitInput = document.getElementById(submitInputElementId);
    }
    
    this.m_searchBoxUniqueId = searchBoxUniqueId;
    
    // elements - set within Bind()
    this.m_popupDiv = null;
    this.m_valueUL = null;
    this.m_values = new Array();    // used to find LI's quickly when highlighting and selecting
    this.m_highlightedIndex = null; // points to the item in m_values that matches the selected item in the UI
    
    // instantiated when an ajax request is made
    this.m_ajaxRequestObj = null;
}

// ------------------------------------------------------------

// wire methods
TypeAhead.prototype.Bind            = TypeAhead_Bind;
TypeAhead.prototype.OnKeyDown       = TypeAhead_OnKeyDown;
TypeAhead.prototype.OnKeyUp         = TypeAhead_OnKeyUp;
TypeAhead.prototype.processResponse = TypeAhead_ProcessAjaxResponse; // "processResponse" is part of an agreed contract with the AsynCommObj
TypeAhead.prototype.handleError     = TypeAhead_HandleError;         // "handleError" is part of an agreed contract with the AsynCommObj
TypeAhead.prototype.Highlight       = TypeAhead_Highlight; 

// ------------------------------------------------------------

// "binds" to the input element specified:
//  - adds elements to the DOM
//  - sets event handlers on the input
function TypeAhead_Bind(){

    // in order to place a popup beneath the input we need to ensure that the input is contained within an element that will provide a solid anchor
    this.m_popupDiv = document.createElement("DIV");

    this.m_input.parentNode.style.position = "relative";
    
    // set non-standard tag to disable browser type-ahead type functionality
    var autocompleteAttribute = document.createAttribute("autocomplete");
    autocompleteAttribute.value = "off";
    this.m_input.attributes.setNamedItem(autocompleteAttribute);
    
    // determine placement of DIV (relative to the input)
    var left = this.m_input.offsetLeft + "px";
    var top = this.m_input.offsetTop + this.m_input.offsetHeight - 1 + "px";
/*    
    alert(this.m_input.parentNode.tagName + ": " + 
            this.m_input.parentNode.style.display + ", " + 
            this.m_input.parentNode.offsetHeight + "\r\n" + 
            this.m_input.tagName + ": " + 
            this.m_input.style.display + ", " + 
            this.m_input.offsetHeight);
*/    
    // style this div so that it won't affect the placement of the input element
    this.m_popupDiv.style.border = "solid 1px #666666";
    this.m_popupDiv.style.background = "#ffffff";
    this.m_popupDiv.style.left = left;
    this.m_popupDiv.style.top = top;
    this.m_popupDiv.style.position = "absolute";
    this.m_popupDiv.style.zIndex = 100;
    
    this.m_popupDiv.style.display = "none";
    
    // insert the DIV into the DOM
    this.m_input.parentNode.insertBefore(this.m_popupDiv, this.m_input);

    // add UL
    this.m_valueUL = document.createElement("UL");
    this.m_valueUL.style.listStyleType = "none";
    this.m_valueUL.style.padding = "0px";
    this.m_valueUL.style.margin = "0px";
    this.m_valueUL.style.fontSize = "8pt";
    this.m_valueUL.style.textAlign = "left";
    this.m_valueUL.style.whiteSpace = "nowrap";
    
    
    var closeA = document.createElement("A");
    closeA.style.display = "block";
    closeA.style.color = "#003300";
    closeA.style.fontSize = "8pt";
    closeA.style.textAlign = "right";
    closeA.style.padding = "0px";
    closeA.style.margin = "2px";
    closeA.style.fontWeight = "normal";
    closeA.href = "javascript:void(0);";
    closeA.innerHTML = "close";
    
    this.m_popupDiv.appendChild(this.m_valueUL);
    this.m_popupDiv.appendChild(closeA);

    // set event handlers
    SetTypeAheadEventHandlers(this.m_input, this);
    SetTypeAheadCloseAnchorEventHandlers(this.m_popupDiv, closeA);
}

// note used
function TypeAhead_OnKeyUp(e){
    // If the previously activated AJAX request has not returned yet, then throw it away
    if (this.m_ajaxRequestObj != null){ this.m_ajaxRequestObj.AbortReq();  }
    
    // get the event (feature detect)
    if (!e) { e = window.event; }
    
    // get the char code (feature detect)
    var unicode = null;
    if(e.keyCode){ unicode = e.keyCode; }else{ unicode = e.charCode; }
    
    // up down and enter are handled on key down
    if((unicode != c_key_enter) && (unicode != c_key_upArrow) && (unicode != c_key_downArrow)){
    
        // send a request for matches IF there is a value
        if((this.m_input.value != null) && (this.m_input.value != "")){
            
            // trigger a new ajax request
            this.m_ajaxRequestObj = new AsynCommObj(this);
            
            var url = this.m_lookupUrl + "?" + this.m_lookupValueParamName + "=" + encodeURIComponent(this.m_input.value);
            this.m_ajaxRequestObj.SendGetReq(url);
        
        }else{
            this.m_popupDiv.style.display = "none";
        }
    
    }
}

// handles the inputs onkeyup event (launches an ajax request passing through the current text value in the QS
function TypeAhead_OnKeyDown(e){

    // get the event (feature detect)
    if (!e) { e = window.event; }
    
    // get the char code (feature detect)
    var unicode = null;
    if(e.keyCode){ unicode = e.keyCode; }else{ unicode = e.charCode; }
    
    if(unicode == c_key_enter){
    
        // check for a select item in the drop down and set this value if there is
        if((this.m_highlightedIndex != null) &&
           (this.m_highlightedIndex >= 0) &&
           (this.m_highlightedIndex < this.m_values.length)){
            
            // set the input's value with that of the highlighted item
            this.m_input.value = this.m_values[this.m_highlightedIndex].m_value;
            // JC, following pattern found elsewhere in script
            __doPostBack(this.m_searchBoxUniqueId,'Submit Search');
        }
        
    }else if(unicode == c_key_upArrow){
    
        if(this.m_highlightedIndex <= 0){
            this.Highlight(null);
        }else{
            // move the highlighted item (NB Highlight call handles range checks)
            this.Highlight(this.m_highlightedIndex-1);
        }
        
    }else if(unicode == c_key_downArrow){
    
        if(this.m_highlightedIndex == null){
            this.Highlight(0);
        }else{
            // move the highlighted item (NB Highlight call handles range checks)
            this.Highlight(this.m_highlightedIndex+1);
        }
        
    }
}

// Handles the response from an AJAX request for qualifcations content (QualificationSelector_ShowQualifications)
function TypeAhead_ProcessAjaxResponse(responseXml){
    
    // clear current values
    this.m_valueUL.innerHTML = "";
    this.m_values = new Array();
    this.m_highlightedIndex = null;
    
    // results are a list of values
    var nodes = null;
    if(responseXml.childNodes != null && responseXml.childNodes.length > 0){

        nodes = responseXml.childNodes;
        
        for (var i = 0; i < nodes.length; i++)
        {
            // JC, for some reason Firefox (possibly others) reports .nodeName in uppercase
            //     regardless of how it's written in the page when the response is xhtml
            //     hence the reason for making the checks case insensitive
            var nodeName = nodes[i].nodeName.toLowerCase();

            if(nodeName == "value"){
        
                var resultNode = nodes[i];
                var content = "";
                if (resultNode.innerHTML != null)
                {
                    content = resultNode.innerHTML;
                }
                else if (resultNode.childNodes.length > 0)
                {
                    content = resultNode.childNodes[0].xml;
                }
                
                // add content to the popup
                var newLI = document.createElement("LI");
                newLI.style.padding = "0px";
                newLI.style.margin = "0px";
                newLI.style.width = "100%";
                
                var newA = document.createElement("A");
                newA.href = "javascript:void(0);";
                newA.innerHTML = content;
                newA.style.textDecoration = "none";
                newA.style.fontWeight = "normal";
                newA.style.color = "#000000";
                newA.style.width = "100%";
                newA.style.margin = "1px 2px 1px 2px";
                newA.style.fontSize = this.m_input.style.fontSize;
                newA.style.display = "block";
                
                SetTypeAheadPopupLIEventHandlers(this, newA, content);
                SetTypeAheadPopupAnchorHighlightEventHandlers(newA, this, i);
                
                this.m_valueUL.appendChild(newLI);
                newLI.appendChild(newA);
                
                // add a new value
                this.m_values.push(new TypeAheadValue(content, newLI, newA));
            }
        }
        
        this.m_popupDiv.style.width = "auto";
        this.m_popupDiv.style.display = "block";
        
        if(this.m_popupDiv.offsetWidth < this.m_input.offsetWidth){
            this.m_popupDiv.style.width = this.m_input.offsetWidth + "px";
        }
        
        
    }else{
        this.m_popupDiv.style.display = "none";
    }
}

// default error handler
function TypeAhead_HandleError(text){
    // hide popup
    this.m_popupDiv.style.display = "none";
    alert(text);
}

// applies a visual effect to show a highlighted item in the UI while tracking it's index
function TypeAhead_Highlight(index){
   
    if(this.m_values.length == 0){ return; }
    
    if((index < 0) || (index >= this.m_values.length)){ 

        return; 
        
    }else{
           // remove any highlighting from any current item
        if(this.m_highlightedIndex != null){
            var value = this.m_values[this.m_highlightedIndex];
            value.m_LI.style.background = "";
        }
        
        // update index
        this.m_highlightedIndex = index;

        // set UI highlight
        if(this.m_highlightedIndex != null){
            var value = this.m_values[index];
            value.m_LI.style.background = "#eeeeee"; //"#cdddd0"
        }
        
    }
}

// ------------------------------------------------------------

// assigns event handlers, seperate function solves problems with the meaning of "this"
function SetTypeAheadEventHandlers(input, typeAhead){
    
    var existing = input.onkeydown;
    input.onkeydown = function(e){
        // key down - this is TypeAhead_OnKeyDown
        typeAhead.OnKeyDown(e);
        // call (preserve) any existing handlers assigned
        if(existing != null){ existing(); }
    }
        
    var existing = input.onkeyup;
    input.onkeyup = function(e){
        // key up - this is TypeAhead_OnKeyUp
        typeAhead.OnKeyUp(e);
        // call (preserve) any existing handlers assigned
        if(existing != null){ existing(); }
    }
    
    var existing = input.onmouseover;
    input.onmouseover = function(e){
        typeAhead.Highlight(null);
        // call (preserve) any existing handlers assigned
        if(existing != null){ existing(); }
    }
}

// assigns event handlers, seperate function solves problems with the meaning of "this"
function SetTypeAheadPopupLIEventHandlers(typeAHead, anchor, value){
    anchor.onclick = function(e){

        typeAHead.m_popupDiv.style.display = "none";
        typeAHead.m_input.value = value;

        /*
        if(typeAHead.m_submitInput != null){
            typeAHead.m_submitInput.onclick(e);
            return false;
        }else{
            typeAHead.m_input.focus(e);
            typeAHead.m_input.form.submit(e);
        }
        */
        
        // JC, following pattern found elsewhere in script
        __doPostBack(typeAHead.m_searchBoxUniqueId,'Submit Search');
    }
}

// assigns event handlers for the hightlight on mouse over, seperate function solves problems with the meaning of "this"
function SetTypeAheadPopupAnchorHighlightEventHandlers(anchor, typeAhead, index){
    anchor.onmouseover = function(e){
        typeAhead.Highlight(index);
    }
}

// assigns event handlers, seperate function solves problems with the meaning of "this"
function SetTypeAheadCloseAnchorEventHandlers(popupDiv, anchor){
    anchor.onclick = function(e){
        popupDiv.style.display = "none";
    }
}
