/*
     $Revision$
     $Id$

     File:        Suggest.js
     Description:
     Parameters:  params.objectName,
                  params.queryField,
                  params.queryUrl,
                  params.initKey,
                  params.initExtraParam,
                  params.keyField,
                  params.fieldReadonly,
                  params.urlAdd,
                  params.extraParamField,
                  params.isBad,
                  params.highlightDescript,
                  params.noJustOne,
                  params.JSONP
                  params.embedded           Kijk nooit naar het parent document (embedded)
     Context:
     Note:        Pas op met gebruiken functies uit andere modules. Dit bestand
                  wordt ook bij vinder/vanuit API\phonebook_js.asp gebruikt en dan
                  heb je die andere bestanden niet.

*/

var $suggestbusy;
var suggesttimer;
function Suggest(inParams)
{
  // Genereer een enkel stukje busy-html die voor alle suggests in deze pagina hergebruiken
  if (!$suggestbusy)
    $suggestbusy = $("<i class='fa fa-gear fa-spin suggestbusy'></i>").appendTo($("body"));
  var params = inParams;

  var storeKeyField; // = inStoreKeyField; // use function withKeyField() to assign field
  var storeExtraParamField; // = instoreExtraParamField; // use function withExtraParamField() to assign field
  var _completeDiv = null;
  var _comboDiv = null;

  //thisdoc.onclick = function(){debugger;};
  // Kopie uit FcltJquery.js. Die hebben we niet vanuit API\phonebook_js.asp
  var myEncode = function (s)
  {
      return unescape(encodeURIComponent(escape(s))).replace(/\+/g, "%2B");
  }

  var req;
  var suggestionValues=[];
  var resultsObj;
  var selected = -1;
  var latestServerQuery = "";
  var THROTTLE_PERIOD = 250;
  var timerID = null;
  var lastHighlight = null;
  var divWidth = 500;

  var emptyKey = ("emptyKey" in params)?params.emptyKey:-1;
  var emptyText = "";
  var currentKey = -1;
  var currentText = null;
  var currentExtraParam = null;
  // De this. functies zijn zo extern ook beschikbaar
  this.makeReadOnly = makeReadOnly;
  this.undoReadOnly = undoReadOnly;
  this.isReadOnly = isReadOnly;
  this.setValue = setValue;
  this.getValue = getValue;
  this.CheckJustOne = CheckJustOne;
  this.reloadUrlAdd = reloadUrlAdd;
  this.changeQueryUrl = changeQueryUrl;
  this.sendQuery = sendQuery;
  thisdoc = document; // Hier wordt de AutocompleteDiv aan gehangen

  // TODO: Controleren of parent wel in hetzelfde domein zit?
  if (!params.embedded && self != top && window.name != "fclttop" && document.body.id!="searchbody")
  {
    thisdoc = parent.document; // We gaan de AutocompleteDiv aan de parent hangen, uit het frame breken

    // Mogelijke fix voor PNBR#22301
    if (FcltMgr._pageManager.name == 'defaultManager' && parent.window.name != "fclttop" && parent.FcltMgr && !parent.FcltMgr.IsTheManager)
      thisdoc = parent.parent.document; // Nog eentje hoger

    if (!window.name || window.name == "")
    {
      // FcltMgr.alert("Internal error. " + document.referrer + " heeft een IFRAME zonder 'name' en 'id' voor " + window.location.pathname);
      thisdoc = document;
      //debugger;
    }
/* Sommige mobile/3G proxies embedden de CSS 'netjes' zodat het bestand niet te vinden is
    var cssfound = false;
    for (i = 0; i < thisdoc.styleSheets.length; i++)
    {
      try
      { // TODO faalt op FF?
        if (thisdoc.styleSheets[i].href.match(/suggest.css/i))
          cssfound = true;
      }
      catch(e){};
    }
    if (!cssfound)
    {
      FcltMgr.alert("Internal error. " + document.referrer + " moet suggest.css ook includen!");
      // debugger;
    }
*/
  }
  var icon = inParams.icon || "fa-asterisk";
  if (params.embedded && params.site)
      var $klikker = $("<div class='suggestklikker' style='cursor:pointer'><img src='" + params.site + "/appl/shared/suggest/suggest.png'></i></div>");
  else
      var $klikker = $("<div class='suggestklikker'><i class='fa fa-fw " + icon + " suggestklikker'></i></div>");
  $(params.queryField).after($klikker)
                      .addClass("hasIcon");

  params.queryField.setAttribute("sgKey", params.initKey);
  // IE6 en IE7 hebben functie hasAttribute niet
  if (!params.queryField.hasAttribute)
      params.queryField.hasAttribute = function (attr) { return !!params.queryField.getAttribute(attr) };

  if (params.keyField)
  {
    storeKeyField = params.keyField;
    //storeKeyField.value = params.queryField.getAttribute("sgKey");
    storeKeyField.value = params.keyField.value;
  }
  else
    this.withKeyField = function (inStoreKeyField)
                        {
                          storeKeyField = inStoreKeyField;
                          //storeKeyField.value = params.queryField.getAttribute("sgKey");
                          storeKeyField.value = params.keyField.value;
                          return this;
                        }

  if (params.extraParamField)
  {
    storeExtraParamField = params.extraParamField;
    if (params.queryField.hasAttribute("sgExtraParam"))
        storeExtraParamField.value = params.queryField.getAttribute("sgExtraParam");
  }
  else
    this.withExtraParamField = function (instoreExtraParamField)
                               {
                                 storeExtraParamField = instoreExtraParamField;
                                 if (params.queryField.hasAttribute("sgExtraParam"))
                                     storeExtraParamField.value = params.queryField.getAttribute("sgExtraParam");
                                 return this;
                               }

  // Kan verwijderd worden. Kan ook bereikt worden door initiële waarde -1 mee te geven
  this.withEmpty = function (eKey, eText)
  {
    emptyText = eText? eText : "";
    emptyKey = eKey;
    return this;
  }

  // Niet extern bedoeld maar noodzakekelijk voor setTimeout

  this.requestLoop = requestLoop;

  //if (params.objectName == "sgPerson") FcltMgr.alert("initKey = " + params.initKey);
  //if (params.initKey == -1) FcltMgr.alert(params.objectName + " " + params.initKey);
  // Alleen als verplicht
  if (params.initKey == -1 && !params.noJustOne && $(params.queryField).hasClass('required'))
    CheckJustOne();

  if (params.fieldReadonly)
  {
    params.queryField.readOnly = true;
    setClass("suggestReadOnly");
  }
  else
    startup();

  if (params.isBad)
  {
    setValue(emptyKey, null, true, false, emptyText);
    setClass("suggestBad");
  }
  else
    this.isBad = function ()
                 {
                   setValue(emptyKey, null, true, false, emptyText);
                   setClass("suggestBad");
                 }

  /**
  *
  * It all starts here!
  *
  */

  function startup() {
      createAutocompletDiv();
      hideAutocompleteDiv();
      params.queryField.autocomplete = "off";
      params.queryField.title = L("lcl_shared_suggest_hint");
      params.queryField.onkeydown = keypressHandler;
      params.queryField.onkeyup = keyupHandler;
      params.queryField.onblur = function ()
          {   if (document.activeElement &&
                  typeof thisdoc.activeElement != "unknown" &&
                  (document.activeElement == params.queryField ||
                   thisdoc.activeElement == _completeDiv ||
                   document.activeElement == _comboDiv ||
                   document.activeElement == $suggestbusy[0] ||
                   document.activeElement == $klikker[0] ||
                   $("body").hasClass("modal")  || // Zou anders in IE9 sluiten bij klikken op scrollbar
                   thisdoc.activeElement.parentElement == _completeDiv))
                  return;
              if (isAutocompleteDivVisible())
              {
                  if (params.queryField.getAttribute("sgCurrentValue") == null)
                      setValues(true);
                  else //houden we de huidige waarde
                      setValue(params.queryField.getAttribute("sgKey"),
                               params.queryField.getAttribute("sgCurrentValue"), false, false,
                               params.queryField.getAttribute("sgExtraParam"));

                  hideAutocompleteDiv();
              }
          };

      // http://stackoverflow.com/questions/3380458/looking-for-a-better-workaround-to-chrome-select-on-focus-bug
      $(params.queryField).focus(function () {
          $(params.queryField).select().mouseup(function (e) {
              e.preventDefault();
              $(this).unbind("mouseup");
          });
          if (!timerID)
              timerID = setTimeout(params.objectName + '.requestLoop(-1);', THROTTLE_PERIOD);
      });

      $klikker.click(function (e)
          {
            if (!isReadOnly())
            {
              // Als je op vergrootglas hebt gedrukt moet checkExist altijd false zijn want je wilt een lijst.
              // Anders krijg je ook het verschijnsel dat je 2 keer moet klikken voordat je de lijst krijgt.
              params.queryField.removeAttribute("checkExist");
              $(params.queryField).focus(); // zodat anderen door onblur verbergen
              if (!isAutocompleteDivVisible())
              {
                  autoOpen();
              }
              else
              {
                  setValue(params.queryField.getAttribute("sgKey"),
                           params.queryField.getAttribute("sgCurrentValue"), false, false,
                               params.queryField.getAttribute("sgExtraParam"));
                      hideAutocompleteDiv();
                  }
              return;
            }
          });
      params.queryField.onmousemove = function (evt)
          {
            if (!isReadOnly())
            {
              evt = evt||window.event;
              var offX = evt.offsetX || evt.layerX; // layerX is FireFox
              if (params.queryField.clientWidth-offX<20) // op de 'combodiv' geklikt
                params.queryField.style.cursor="pointer";
              else
                params.queryField.style.cursor="auto";
            }
          };
      params.queryField.setAttribute("sgCurrentValue", params.queryField.defaultValue);
      requestLoop(0);
  }

  function setClass(oneClass)
  {
    $(params.queryField).removeClass("suggest suggestBad suggestBusy suggestReadOnly");
    $(params.queryField).addClass(oneClass);
  }

  function makeReadOnly()
  {
    params.queryField.readOnly = true;
    setClass("suggestReadOnly");
    params.queryField.title = "";
  }

  function undoReadOnly()
  {
    params.queryField.readOnly = false;
    setClass("suggest");
    params.queryField.title = L("lcl_shared_suggest_hint");
  }

  function isReadOnly()
  {
    return params.queryField.readOnly;
  }

  function setValue(key, txt, doonChange, checkExist, extraParam, lastTry)
  {
      // Onthoudt de huidige waarde als je checkt of de te zetten waarde bestaat.
      // Als de nieuwe waarde niet voorkomt in de query en niet kan worden gezet
      // dan wordt geprobeerd om de huidige waarde weer opnieuw te zetten.
      // In dat laatste geval is dan lastTry == true. Indien ook de huidige waarde niet
      // meer aanwezig is in de query, dan wordt de suggestbox leeg gemaakt.
      // Als alleen de nieuwe waarde moet worden geprobeerd,
      // kan ook direct voor lastTry de waarde true worden meegegeven.

      if (checkExist && !lastTry) {
        currentKey = params.queryField.getAttribute("sgKey");
        currentText = params.queryField.value;
        currentExtraParam = params.queryField.getAttribute("sgExtraParam");
      }
      if (params.queryField)
      {
        // Afhankelijk van het readonly zijn de suggest class zetten zodat de suggestBusy wordt vervangen.
        // De suggest class toont het vergrootglas in beeld.
        if (!isReadOnly())
          setClass("suggest");
        params.queryField.setAttribute("sgKey", key);
        if (storeKeyField) storeKeyField.value = key;
        if (extraParam != null) // De waarde 0 moet niet als false worden opgevat.
        {
          params.queryField.setAttribute("sgExtraParam", extraParam);
          if (storeExtraParamField) storeExtraParamField.value = extraParam;
        } else {
          if (storeExtraParamField) storeExtraParamField.value = ""; // Als storeExtraParamField bestaat dan leeg maken (vullen met "")
        }
        if (!checkExist && txt != null)
        {
          params.queryField.value = txt;
          params.queryField.setAttribute("sgCurrentValue", txt);
          //latestServerQuery = txt.toLowerCase();
          if (txt != "" &&
              (document.activeElement && (typeof thisdoc.activeElement != "unknown") &&
               (document.activeElement == params.queryField ||
                thisdoc.activeElement == _completeDiv ||
                thisdoc.activeElement == _comboDiv ||
                thisdoc.activeElement.parentElement == _completeDiv
               )
              )
             )
            // selectRange zou focus stelen
            // Alleen focus stelen als element al focus had (activeElement)
            selectRange(0,0);
        }
        // bij 'checkExist' hoeven we nu nog geen onChange te doen: die komt later nog wel
        if (!checkExist && doonChange && params.queryField.getAttribute("sgonChange"))
        {
            eval(params.queryField.getAttribute("sgonChange")).call(null, key, txt, {suggestid: params.queryField.id});
        }
        if (checkExist)
          params.queryField.setAttribute("checkExist", true);
        else
          params.queryField.removeAttribute("checkExist");

        if (doonChange)
          params.queryField.setAttribute("doonChange", doonChange);
        else
          params.queryField.removeAttribute("doonChange");
        if (checkExist) {
          sendQuery(txt, false, checkExist);
        }
      }
  }

  function getValue()
  {
      return (params.queryField.getAttribute("sgKey"));
  }

  function process_one_choice_info(data, textStatus)
  {
    if (textStatus == "success")
    {
      // setValue(key, txt, doonChange, checkExist, extraParam, lastTry)
      if (data && data.result.length == 1)
      {
        var txt = data.result[0].txt;
        var key = data.result[0].key;
        var extraParam = data.result[0].extra;
        setValue(key, txt, true, false, extraParam);
      }
      // initiele waarden worden met initkey gezet
      //else
        //setValue(emptyKey, emptyText, true, false, emptyText); // Er zijn geen waarden. Suggestbox leeg maken.
    }
    else
      FcltMgr.alert("process_one_choice_info: " + textStatus);
  }

  // Een urlAdd kan zijn meegegeven als val, field of jqfield
  function _urlAddParams(urlAdd)
  {
    if (!urlAdd)
      return "";

    var urlAddParams = "";
    for (var i = 0; i < urlAdd.length; i++)
    { // Url parameter aan link toevoegen
        urlAddParams += "&" + urlAdd[i].urlParam + "=";
        var theVal = urlAdd[i].val;
        if (typeof theVal == "undefined")
        {
            var selector = urlAdd[i].jqfield;
            var hasselector = "";
            if (!selector || selector == "") // JQuery selector, handig voor radio
            {
              selector = "#" + urlAdd[i].field;
              hasselector = "#has_" + urlAdd[i].field;
            }
            // Is het misschien een checkbox? Dan is de waarde niet belangrijk maar of deze is aangevinkt.
            if (hasselector != "" && $(hasselector).length > 0)
            { // Het is een checkbox. Controleer of de checkbox is aangevinkt.
              theVal = $(selector).prop("checked")? 1 : 0; // Wel (1) of niet (0) aangevinkt.
            }
            else
              theVal = $(selector).val()? $(selector).val() : -1; // Indien niets geselecteerd dan -1;
        }
        urlAddParams += theVal;
    }
    return urlAddParams;
  }

  function CheckJustOne()
  {
    var url = params.queryUrl + _urlAddParams(params.urlAdd) + "&k=*";
    // eerste 10 is méér dan genoeg url += "&SuggestAll=1&";

    $.getJSON(url, process_one_choice_info);
  }

  function reloadUrlAdd(newUrlAdd) {
    params.urlAdd = newUrlAdd;
  }

  function changeQueryUrl(new_inQueryUrl) {
    params.queryUrl = new_inQueryUrl;
    latestServerQuery = null;
    // gaat mis bij kostenplaats fe res setValue(-1, ""); // TODO: vriendelijk via checkExist?
  }

  function calculateOffsetLeft(r){
    return SumAttr(r,"offsetLeft")
  }

  function calculateOffsetTop(r){
    return SumAttr(r,"offsetTop")
  }

  function SumAttr(r,attr){
    var kb = 0;
    while(r)
    {
      kb += r[attr];
      r = r.offsetParent
    }
    return kb
  }

  function createAutocompletDiv()
  {
      //_completeDiv=$(thisdoc).find("div#"+params.objectName+"completeDiv")[0];
      // if (true||!_completeDiv)
      {
          _completeDiv=thisdoc.createElement("DIV");

          _completeDiv.id=params.objectName+"completeDiv";
          _completeDiv.className = "suggestautocompleteContainer";
          _completeDiv.onblur = function () { params.queryField.onblur(); }
          _completeDiv.style.zIndex="2199"; // JQuery dialog begint bij 1000, daar zitten we ruim boven

          thisdoc.body.appendChild(_completeDiv);
          //_completeDiv.appendChild(thisdoc.createTextNode("DIVVERtje"));
      }
      // pas bij echt openen afmetingen zetten setCompleteDivSize();
  }

  function setCompleteDivSize()
  {
    var _inputField = params.queryField;

    if(_inputField && _completeDiv)
    {
      var newdivWidth  = Math.min(divWidth,  thisdoc.body.offsetWidth); // Voor Mobile
      var newLeft = calculateOffsetLeft(_inputField);
      var newTop =calculateOffsetTop(_inputField)+_inputField.offsetHeight-1;
      if (thisdoc != document)
      {
          newTop += calculateOffsetTop(thisdoc.getElementById(window.name))+2;
          newLeft += calculateOffsetLeft(thisdoc.getElementById(window.name))+2;
      }
      newTop -= $(document).scrollTop();
      if (newLeft + newdivWidth > thisdoc.body.offsetWidth)
      {   // Wel in beeld houden
          newLeft = Math.max(0, thisdoc.body.offsetWidth - newdivWidth - 20); // ook speling voor scrollbar
      }
      _completeDiv.style.left=newLeft+"px";
      _completeDiv.style.top=newTop+"px";

      _completeDiv.style.width=newdivWidth + "px";
    }
  }

  function doRequest(keyword, bAll)
  {
      var pos = $(params.queryField).next().position();
      $suggestbusy.css("top", pos.top);
      $suggestbusy.css("left", pos.left);
      window.suggesttimer = setTimeout(function () { $suggestbusy.show(); }, 500);

      sendQuery(keyword, bAll);
      latestServerQuery = keyword;
  }

  var ESCAPE = 27;
  var KEYUP = 38;
  var KEYDOWN = 40;
  var KEYENTER = 13;
  var KEYTAB = 9;
  var KEYPGUP = 33;
  var KEYPGDOWN = 34;
  // Deze functie is via een timer gescheduled
  function requestLoop(toetskey)
  { // Toetskey
    // -1: Geen toets, focus op veld
    //  0: Initialisatie
    // >0: Toets aangeklikt

    // Als de suggest readonly is hoef ik niet verder te kijken.
    if (isReadOnly()) return;
    setClass("suggest");

    timerID = null;

    if (params.queryField.value == "" && params.queryField.getAttribute("sgKey") != emptyKey)
      setValue(emptyKey, emptyText, true, false, emptyText);

    // Bepaal wat de gebruiker heeft ingetikt exclusief het deel wat gehighlight is
    var keyword = query().toLowerCase();

    // Is het een te snelle tab zonder popup selectie. Dan is er geen waarde gezet. Focus terug.
    var isToFastTab = (toetskey == KEYTAB && (params.queryField.value != params.queryField.getAttribute("sgCurrentValue")))
    if (isToFastTab)
      params.queryField.select();

    var isUpDownKey = (toetskey == KEYUP || toetskey == KEYDOWN || toetskey == KEYPGUP || toetskey == KEYPGDOWN);
    var isNoSpecialKey = (toetskey > 0 && !isUpDownKey && toetskey != KEYENTER && toetskey != KEYTAB);
    // Bij toestsaanslag altijd popup scherm. Ook bij default waarde. Omschrijving tekst kan vaker voorkomen.
    if (toetskey != 0 &&
        (keyword!=latestServerQuery) &&
        (keyword != '') &&
        ((params.queryField.value != params.queryField.defaultValue) || (toetskey > 0 && !isUpDownKey)) &&
        (params.queryField.value != params.queryField.getAttribute("sgCurrentValue") || isNoSpecialKey))
    {
        doRequest(query().toLowerCase(), false);
    }

    if (params.queryField.value == '' || params.queryField.value == params.queryField.defaultValue)
    {
      hideAutocompleteDiv();
      suggestionValues = [];
      latestServerQuery = null;
      selected = 0;
      if (params.queryField.value == params.queryField.defaultValue && toetskey == 0)
        setValue(params.initKey, null, false, false, params.initExtraParam)
      else if (params.queryField.value == "" && params.queryField.getAttribute("sgKey") != emptyKey)
        setValue(emptyKey, emptyText, true, false, emptyText);
      if (keyword == "" && toetskey > 0)
      { // Eventuele sgCascade uitvoeren als suggest veld wordt leeg gemaakt.
        eval(params.queryField.getAttribute("sgCascade"));
      }
    }
  }

  // Bepaal wat de gebruiker heeft ingetikt exclusief het deel wat gehighlight is
  function query()
  {
    var textbox = params.queryField;
    var N;
    if(textbox.createTextRange) // IE
    { // IE6/7 soms bij het opstarten: htmlfile: unspecified error. Daarom de catch
      try
      {
        var fa = document.selection.createRange().duplicate();
        N = fa.text.length;
      }
      catch(e)
      {
        N=0;
      }
    }
    else
      if(textbox.setSelectionRange) // Mozilla
      {
        try
        {
          N = textbox.selectionEnd-textbox.selectionStart;
        }
        catch(e)  // Firefox-->Error
        {
          N = 0;
        }
      }
    return textbox.value.substring(0, textbox.value.length-N);
  }

  function sendQuery(txt, bAll, checkExist)
  {
    var url = params.queryUrl + _urlAddParams(params.urlAdd) + "&k=" + myEncode(txt);
    if (bAll) url += "&SuggestAll=1";
    if (checkExist) url += "&checkExist=1";

    jQuery.ajax({type: "GET",
                 url: url,
                 dataType: params.JSONP? "jsonp" : "json",
                 success: process
                });
  }

  // Callback functie van HTTP Request
  function process(data, textStatus)
  {
    clearTimeout(window.suggesttimer);
    $suggestbusy.hide();

    var checkExist = params.queryField.hasAttribute("checkExist");
    var doonChange = params.queryField.getAttribute("doonChange");

    // Afhankelijk van het readonly zijn de suggest class zetten zodat de suggestBusy wordt vervangen.
    // De suggest class toont het vergrootglas in beeld.
    if (!isReadOnly())
      setClass("suggest");

    showAutocompleteDiv();
    resultsObj = data;
    selected = resultsObj.isAll? Math.min(resultsObj.result.length - 1, 10 - 1) : 0;

    // voorheen htmlFormat(checkExist, doonChange), nu ingeplakt

    lastHighlight = null;
    while(_completeDiv.childNodes.length > 0)
    {
      _completeDiv.removeChild(_completeDiv.childNodes[0]);
    }

    if (!resultsObj.result.length) // Hmm, geen resultaten gevonden
    {
      if (params.queryField.value.length > 0 && params.queryField.value != "*")
      {
        // Als de nieuwe waarde niet voorkomt in de query en niet kan worden gezet
        // dan wordt geprobeerd om de huidige waarde weer opnieuw te zetten (currentKey > 0).
        // Indien ook de huidige waarde niet meer aanwezig is in de query,
        // dan wordt de suggestbox leeg gemaakt.
        if (checkExist && currentKey > 0)
        {
          var pcurrentKey = currentKey;
          var pcurrentText = currentText;
          var pcurrentExtraParam = currentExtraParam;
          currentKey = emptyKey;
          currentText = "";
          currentExtraParam = null;
          setValue(pcurrentKey, pcurrentText, doonChange, true, pcurrentExtraParam, true);
        }
        else {
          setValue(emptyKey, (checkExist? "" : null), true, false, (checkExist? "" : null));
        }
        if (!checkExist)
        {
          setClass("suggestBad");
        }
      }
      else
      { // Geen resultaten gevonden en params.queryField was leeg.
        // Zet nu opnieuw de lege waarde.
        setValue(-1, "", true, false, null, true);
      }
      hideAutocompleteDiv();
      return;
    }

    // Maak suggestbox leeg indien checkExist is true en gevonden key en ingevoerde key komen niet overeen:
    if (checkExist && resultsObj.result.length == 1 && resultsObj.result[0].key != params.queryField.getAttribute("sgKey"))
    { // Maak suggestbox leeg
      if (params.queryField.value.length > 0)
      {
        setValue(emptyKey, "", true, false, "");
        setClass("suggestBad");
      }
      hideAutocompleteDiv();
      return;
    }
    // Indien checkExist is true en er zijn meerdere waarden gevonden (Begin gedeelte van de omschrijving komt overeen) dan:
    // 1) Komt maar van 1 van deze de gevonden key en ingevoerde key overeen, dan deze selecteren
    // 2) Anders selectbox leeg maken.
    else if (checkExist && resultsObj.result.length > 1)
    {
      var nr_of_hits = 0;
      for (var i = 0; i < resultsObj.result.length; i++)
      {
        if (resultsObj.result[i].key == params.queryField.getAttribute("sgKey"))
        {
          nr_of_hits++;
          var hit_i = i;
        }
      }
      if (nr_of_hits == 1)
      { // Geselecteerde text selecteren (enige overeenkomende resultaat van de meerdere gevonden waarden)
        selected = hit_i; // Kies wel de juiste overeenkomende resultaat met de juiste key
        setValues(doonChange);
        if (!checkExist) selectRange(0,0);
        hideAutocompleteDiv();
        return;
      }
      else
      { // Gevonden key en ingevoerde key komen vaker overeen. Maak suggestbox leeg
        if (params.queryField.value.length > 0)
        {
          setValue(emptyKey, "", true, false, "");
          setClass("suggestBad");
        }
        hideAutocompleteDiv();
        return;
      }
    }

    if (checkExist && resultsObj.result.length == 1 && resultsObj.result[0].key == params.queryField.getAttribute("sgKey"))
    {
      // geselecteerde text selecteren (enige resultaat)
      setValues(doonChange);
      if (!checkExist) selectRange(0,0);
      hideAutocompleteDiv();
      return;
    }


    suggestionValues = [];
    if (params.queryField.getAttribute("sgCurrentValue") && params.queryField.getAttribute("sgKey") > 0)
    {
      // Plak de current er voor als eerste keuze
      resultsObj.result = [ { txt: params.queryField.getAttribute("sgCurrentValue"),
                              key: params.queryField.getAttribute("sgKey"),
                            desc: L("lcl_shared_suggest_huidig")}].concat(resultsObj.result);
    }

    if (resultsObj.similar)
      $(_completeDiv).addClass("suggestsimilar");
    else
      $(_completeDiv).removeClass("suggestsimilar");

    for (var i = 0; i < resultsObj.result.length; i++)
    {
      if (resultsObj.result[i].txt)
      {
        suggestionValues.push(resultsObj.result[i].txt);

        var xDiv = thisdoc.createElement("div");
        xDiv.sgIndex = i;
        if (i==selected)
        {
          xDiv.className = "suggestsrs";
          lastHighlight = xDiv;
        }
        else
        {
          xDiv.className = "suggestsr";
        }

        xDiv.onmouseover = function() { highlight(this.sgIndex, this); return false; };
        xDiv.onmousedown = function() { setValues(true);hideAutocompleteDiv(); return true; };
        if ("title" in resultsObj.result[i])
          xDiv.title = resultsObj.result[i].title;
        if (resultsObj.anyfoto)
        {
            var fotourl = resultsObj.result[i].foto;
            if (fotourl && fotourl.match(/\.(png|jpg|jpeg)$/i)) // het is een image
            {
              var xFoto = thisdoc.createElement("img");
              xFoto.setAttribute("src", fotourl);
              xFoto.setAttribute("height", "40");
              xFoto.className = "suggestfoto";
              xDiv.appendChild(xFoto);
            }
            else
            {
              var xFoto = thisdoc.createElement("div");
              xFoto.innerHTML = "&nbsp;";
              xFoto.className = "suggestfoto";
              xDiv.appendChild(xFoto);
            }
        }
        var xSpan = thisdoc.createElement("span");
        xSpan.className = "suggestsrt";
        if (i == 0 &&
            params.queryField.getAttribute("sgCurrentValue") &&
            params.queryField.getAttribute("sgKey") > 0) // eerste krijgt aparte opmaak
        {
          xSpan.style.borderBottom = "1px solid black"; // TODO: Stijl voor verzinnen
          xSpan.style.paddingBottom = "10px";
        }

        var ll = 0;
        // first matching part bold
        var lsq = latestServerQuery;
        if (lsq && lsq.charAt(0) == "*") lsq = lsq.substring(1);
        ll = resultsObj.result[i].txt.toLowerCase().indexOf(lsq);
        if (resultsObj.result[i].isHTML)
          xSpan.innerHTML = resultsObj.result[i].txt;
        else if (ll >= 0 && lsq.length)
        {
          if (ll > 0)
            xSpan.appendChild(thisdoc.createTextNode(resultsObj.result[i].txt.substring(0, ll)));
          var xb = thisdoc.createElement("b");
          xb.appendChild(thisdoc.createTextNode(resultsObj.result[i].txt.substring(ll, ll + lsq.length)));
          xSpan.appendChild(xb);
          xSpan.appendChild(thisdoc.createTextNode(resultsObj.result[i].txt.substring(ll + lsq.length)));
        }
        else
          xSpan.appendChild(thisdoc.createTextNode(resultsObj.result[i].txt));

        xDiv.appendChild(xSpan);

        if ("desc" in resultsObj.result[i]) // Extra description present
        {
          var x3 = thisdoc.createElement("span");
          x3.className = "suggestsrc";
          var desc = resultsObj.result[i].desc || "";
          ll = desc && desc.toLowerCase().indexOf(lsq);
          if (params.highlightDescript && ll >= 0)
          {
            x3.appendChild(thisdoc.createTextNode(desc.substring(0, ll)));
            var xb = thisdoc.createElement("b");
            xb.appendChild(thisdoc.createTextNode(desc.substring(ll, ll + lsq.length)));
            x3.appendChild(xb);
            x3.appendChild(thisdoc.createTextNode(desc.substring(ll + lsq.length)));
          }
          else
            x3.appendChild(thisdoc.createTextNode(desc));
          xDiv.appendChild(x3);
        }

        _completeDiv.appendChild(xDiv);
      }
    }

    // xDiv is op dit moment de laatste
    if (!xDiv || (xDiv.offsetTop + xDiv.clientHeight < 300))
      _completeDiv.style.height = ""; // Auto
    else
    { //_completeDiv.style.overflow = "hidden";
      _completeDiv.style.height = "300px";
      if (/MSIE 7.0/.test(navigator.userAgent))
        _completeDiv.style.paddingRight = "16px"; // Anders valt bij IE7 een deel achter de scrollbar
      _completeDiv.style.overflowY = "auto";
      _completeDiv.style.overflowX = "hidden";

    }

    if (resultsObj.hasMore) // Is gezet als er 10 of meer records zijn
    {
      var xDiv = thisdoc.createElement("div");

      xDiv.className = "suggestsr";
      var xSpan = thisdoc.createElement("span");
      xSpan.className = "suggestMore";
      if (resultsObj.isAll)
        xSpan.appendChild(thisdoc.createTextNode(resultsObj.hasMoreMsg)); // Maximum bereikt
      else
      {
        $(xDiv).mousedown(function (evt) {
                doRequest(latestServerQuery, true);
                evt.preventDefault(); // Voorkom blur
              });
        xSpan.appendChild(thisdoc.createTextNode(resultsObj.hasMoreMsg)); // Er is meer
      }
      xDiv.appendChild(xSpan);
      _completeDiv.appendChild(xDiv);
    }
    //requestSuggestions();
  }

  function keyupHandler (evt)
  {
    // make sure we have a valid event variable
    if(!evt && window.event) {
        evt = window.event;
    }
    var key = evt.keyCode;

    if (key < 32 || (key >= 33 && key <= 46) || (key >= 112 && key <= 123))
    {
      //ignore
    }
    else
    {
      //request suggestions from the suggestion provider
      // Backspace key(8), Delete key (48)
      if (key != 8 && key != 48)
      {
        requestSuggestions();
      }
    }
    return true;
  }

  function keypressHandler (evt)
  {
    params.queryField.removeAttribute("checkExist");
    params.queryField.setAttribute("doonChange",  true);

    // make sure we have a valid event variable
    if (!evt && window.event)
    {
      evt = window.event;
    }
    var key = evt.keyCode;

    // if this key isn't one of the ones we care about, just return

    if (timerID) // reset oude. Is goed voor de performance/response
      clearTimeout(timerID);
    timerID = setTimeout(params.objectName + ".requestLoop(" + key + ");", THROTTLE_PERIOD);

    // don't do anything if the div is hidden
    if (!isAutocompleteDivVisible())
    {
      if (key == KEYDOWN || key == KEYPGDOWN)
        autoOpen();
      return;
    }

    if (key == KEYPGDOWN)
    {
      if (resultsObj.hasMore && !resultsObj.isAll)
      {
        doRequest(latestServerQuery, true);
      }
      else if (isAutocompleteDivVisible)
      {
        _completeDiv.scrollTop += _completeDiv.clientHeight;
        //selected += 20;
        //highlight(selected);
      }
      return false;
    }
    if (key == KEYPGUP)
    {
      if (isAutocompleteDivVisible)
      {
        _completeDiv.scrollTop -= _completeDiv.clientHeight;
        //selected -= 20;
        //highlight(selected);
      }
      return false;
    }
    if (key == KEYUP)
    {
      if ((selected-1) >= 0)
        selected = selected -1;
      else if (!resultsObj.hasMore)
        selected = resultsObj.result.length-1;
      highlight(selected);
      return false;
    }

    if (key == KEYDOWN)
    {
      if ((selected + 1) < resultsObj.result.length)
        selected = selected + 1;
      else if (resultsObj.hasMore && !resultsObj.isAll)
        doRequest(latestServerQuery, true);
      else
        selected = 0;
      highlight(selected);
      return false;
    }

    if (key == ESCAPE)
    {
      params.queryField.onblur();
    }
    if (key == KEYENTER)
    {
      setValues(true);
      selectRange(0,0);
      hideAutocompleteDiv();
      return false;
    }

    return true;
  }

  // De gewone scrollIntoView() heeft wel eens de neiging de complete pagina ook te scrollen
  function myScrollIntoView(theDiv, toView)
  {
    if (theDiv.scrollTop + theDiv.clientHeight < toView.offsetTop + toView.clientHeight)
      theDiv.scrollTop = toView.offsetTop + toView.clientHeight - theDiv.clientHeight;
    if (theDiv.scrollTop > toView.offsetTop)
      theDiv.scrollTop = toView.offsetTop;
  }

  function highlight(number)
  {
    selected = number;
    // Herstel vorige highlight
    if (lastHighlight && lastHighlight.className)
      lastHighlight.className = "suggestsr";
    // Zet nieuwe highlight
    var ieDIVs = $(_completeDiv).find('div').not(".suggestfoto");
    lastHighlight = ieDIVs[selected];
    if (lastHighlight && lastHighlight.className)
      lastHighlight.className = "suggestsrs";
    myScrollIntoView(_completeDiv, lastHighlight);
  }

  function setValues(doonChange)
  {
    if (resultsObj)
    {
      var result = resultsObj.result[selected][resultsObj.storeDesc? "desc" : "txt"];
      setValue(resultsObj.result[selected].key, result, doonChange, false, resultsObj.result[selected].extra) // doonChange
    }
  }

  function autoOpen()
  {
    params.queryField.value = latestServerQuery || "*"; //"*";
    doRequest(params.queryField.value, false);
    if (params.queryField.createTextRange)
    {
        var oRange = params.queryField.createTextRange();
        oRange.move("character", 1);
        oRange.select();
    }
    // TODO: else wat voor FireFox?
    //set focus back to the textbox (when field is not disabled)
    //if (!textbox.disabled) textbox.focus();

    //params.queryField.focus();
    //selectRange(0,1);
  }

  function showAutocompleteDiv()
  {
    setCompleteDivSize(); // voor de zekerheid
    _completeDiv.style.display="block";
  }

  function hideAutocompleteDiv()
  {
    _completeDiv.style.display="none";
  }

  function isAutocompleteDivVisible()
  {
    return (_completeDiv.style.display == "block");
  }

  function requestSuggestions()
  {
    var sTextboxValue =  query().toLowerCase();
    var aSuggestions = [];

    if (suggestionValues.length > 0)
    { //search for matching states
      for (var i=0; i < suggestionValues.length; i++)
      {
        if (suggestionValues[i].toLowerCase().indexOf(sTextboxValue) == 0)
          aSuggestions.push(suggestionValues[i]);
      }
      autosuggest(aSuggestions);
    }
  };

  function autosuggest(aSuggestions)
  { //make sure there's at least one suggestion
    if (aSuggestions.length == 1)
      typeAhead(aSuggestions[0]);
  }

  function typeAhead(sSuggestion)
  {
    var textbox = params.queryField;
    //check for support of typeahead functionality
    if (textbox.createTextRange || textbox.setSelectionRange)
    {
      var iLen = query().length;
      textbox.value = sSuggestion;
      selectRange(iLen, sSuggestion.length);
    }
  };

  function selectRange(iStart, iLength)
  {
    var textbox = params.queryField;
    //use text ranges for Internet Explorer
    if (textbox.createTextRange)
    {
      var oRange = textbox.createTextRange();
      oRange.moveStart("character", iStart);
      oRange.moveEnd("character", iLength - textbox.value.length);
      oRange.select();
      //use setSelectionRange() for Mozilla
    }
    else if (textbox.setSelectionRange)
    {
      textbox.setSelectionRange(iStart, iLength);
    }

    //set focus back to the textbox (when field is not disabled)
    if (!textbox.disabled) textbox.focus();
  };
}