//
// mgbox.js
//
// All mgBox related functions.
// There should not be any need to modify this file,
// customisation can be achieved through various
// "hook" functions.
//
// Feel free to use as you please, but please retain this
// copyright notice.
//
// 2005-02-10 MGrill Version 1.00
// 2005-02-17 MGrill added onLoad hook
//
// see http://www.dcs.lancs.ac.uk/~grill/mgbox/
//

// This is the mgBox class declaration.
// The one and only global mgBox object will be called "theMgBox",
// this object will be instantiated further down.
function mgBox() {
  if(typeof MGBOX_ASSIST_CGI_TEMPLATE == 'undefined') {
    this.ASSIST_CGI_TEMPLATE = "cgi/assist_@.php?s=";
  } else {
    this.ASSIST_CGI_TEMPLATE = MGBOX_ASSIST_CGI_TEMPLATE;
  }
  this.IDLETIME = 300;  // after this duration we start processing the user input!
  this.timerid=null;    // will hold the timer that waits until the user does nothing for IDLETIME ms.
  this.oldcontents="";  // the previous contents of the text box
  this.textboxid="";    // our textbox - will get updated in call to onFocus() later on...
  this.textfieldhandle=null;  // handle to our textbox, see above
  this.httprequest = createXMLHttpRequest();  // the one and only active httprequest object...
  this.httprequestactive = false;
  this.highlightedline = null;
  this.html_text_array = Array(); // results from database
  this.raw_text_array = Array();  // queries go into
  this.id_array = Array();        // these three arrays
  this.isOpenAndFilled = false;
}

// gets called whenever a keydown event happens
function MgOnKeyDown(event) {
  // we process the key. if it is a "normal" character, we only react after
  // a specified timeout (see below).
  // "event" only gets set if the handler was assigned from within
  // javascript, not in the html code. this is done in onFocus().
  if(!event) {
    event = window.event;
  }
  var hBox = getElem("id","mgBox");

  ///////////////// DOWN //////////////////
  if(event.keyCode==40) {  // "down"
    if(!theMgBox.isOpenAndFilled) {
      theMgBox.timeoutReached(theMgBox.textboxid);
    } else {
      if(theMgBox.highlightedline!=null) {
        // change currently hightlighted line back to normal
        hBox.childNodes[0].childNodes[theMgBox.highlightedline].className="";
        // advance selection by one
        theMgBox.highlightedline = (theMgBox.highlightedline+1) % theMgBox.html_text_array.length;
      } else {
        if (theMgBox.html_text_array.length>0) {
          theMgBox.highlightedline = 0;
        }
      }
      // highlight the new line
      hBox.childNodes[0].childNodes[theMgBox.highlightedline].className="mgHighlighted";
    }
  }

  ///////////////// UP //////////////////
  if(event.keyCode==38) {  // "up"
    if(!theMgBox.isOpenAndFilled) {
      theMgBox.timeoutReached(theMgBox.textboxid);
    } else {
      if(theMgBox.highlightedline!=null) {
        // change currently hightlighted line back to normal
        hBox.childNodes[0].childNodes[theMgBox.highlightedline].className="";
        // advance selection by one (upwards)
        theMgBox.highlightedline = (theMgBox.highlightedline==0) ?
                                   theMgBox.html_text_array.length-1
                                   : theMgBox.highlightedline-1;
      } else {
        theMgBox.highlightedline = theMgBox.html_text_array.length-1;
      }
      // highlight the new line
      hBox.childNodes[0].childNodes[theMgBox.highlightedline].className="mgHighlighted";
    }
  }

  ///////////////// ENTER //////////////////
  if(event.keyCode==13) {  // "ENTER"
    if(!theMgBox.isOpenAndFilled) {
      theMgBox.timeoutReached(theMgBox.textboxid);
    } else {
      theMgBox.hideMgBox();

      // copy text into edit field
      var hTextbox = getElem("id",theMgBox.textboxid);
      hTextbox.value = theMgBox.raw_text_array[theMgBox.highlightedline];
      theMgBox.oldcontents = theMgBox.raw_text_array[theMgBox.highlightedline];

      // Remember author ID in hidden input field (if it exists)
      var hHiddenIdBox = getElem("id",theMgBox.textboxid+"_id");
      if( hHiddenIdBox != null ) {
        hHiddenIdBox.value = theMgBox.id_array[theMgBox.highlightedline];
      }

      // call hook if present
      if(typeof mgBox_onEnter_hook == 'function') {
        mgBox_onEnter_hook(theMgBox.textboxid);
      }
      return false;
    }
  }
}
mgBox.prototype.onKeyDown = MgOnKeyDown;

function MgOnFocus(event) {
  // we store our parent id within the MgBox object, so that later on we
  // can check whether a delayed result does actually belong to the currently
  // active box
  // we also reset some other properties

  // first we need to find out the id of our textfield, though:
  if( event == null ) {
    event = window.event;
  }
  var hElement = ( event.srcElement ) ? event.srcElement : event.originalTarget;
  var id=hElement.id;

  theMgBox.textboxid = id;
  if( !id ) {
    alert( 'Error: every mgBox needs a unique ID attribute!' );
  }
  theMgBox.httprequestactive = false;
  theMgBox.textfieldhandle = undefined;
  theMgBox.textfieldhandle = getElem("id",id);
  theMgBox.oldcontents= theMgBox.textfieldhandle.value;  // old contents = current contents

  // we attach the keydown handler
  theMgBox.textfieldhandle.onkeydown = theMgBox.onKeyDown;
  theMgBox.textfieldhandle.onkeyup = theMgBox.onKeyUp;
  theMgBox.textfieldhandle.onblur = theMgBox.onBlur;
}
mgBox.prototype.onFocus = MgOnFocus;

function MgOnBlur(event) {
  theMgBox.textfieldhandle.onkeydown = null;
  theMgBox.textfieldhandle.onkeyup = null;
  theMgBox.textfieldhandle.onblur = null;
  theMgBox.hideMgBox();

  // call hook function if it exists
  if(typeof mgBox_onBlur_hook == 'function') {
    mgBox_onBlur_hook(theMgBox.textboxid);
  }

  // reset some MgBox related properties to make sure it
  // doesn't suddenly pop up!
  theMgBox.textboxid = "";
  theMgBox.oldcontents = "";
  theMgBox.httprequestactive = false;
  theMgBox.handle = null;
}
mgBox.prototype.onBlur = MgOnBlur;

function MgOnKeyUp(event) {
  // we process the results of the previous keypress.
  // "event" only gets set if the handler was assigned from within
  // javascript, not in the html code. this is done in onFocus().
  if(!event) {
    event = window.event;
  }
  var id=theMgBox.textboxid;
  var mytextbox = getElem("id",id);
  var textfieldcontents = mytextbox.value;

  if(textfieldcontents!=theMgBox.oldcontents) {
    // execute new query to fill listbox
    theMgBox.oldcontents = textfieldcontents;

    // call hook if present
    if(typeof mgBox_onContentsChange_hook == 'function') {
      mgBox_onContentsChange_hook(id);
    }

    // We also try to parse the entry if a suitable
    // parsing function exists
    var parse_function = "mgBox_parse_"+id.removeTrailingDigitsIfPresent()+"_field";
    if (eval("typeof "+parse_function)=='function') {
      eval(parse_function+"('"+id+"')");
    }

    // we reset our timeout timer (search will only spring into action when the user does nothing
    // for IDLETIME ms so that we don't slow down the user experience.
    if (theMgBox.timerid!=null) {
      clearTimeout(theMgBox.timerid);
    }
    theMgBox.timerid = setTimeout("theMgBox.timeoutReached('"+theMgBox.textboxid+"')",theMgBox.IDLETIME);
  }
}
mgBox.prototype.onKeyUp = MgOnKeyUp;

function MgTimeoutReached(id) {
  // user was idle for IDLETIME ms, do some processing!
  this.timerid = null;

	// has the focus changed in the meantime?
  if (id!=theMgBox.textboxid) {
    return;
  }

  // we update the listbox if the contents of the text field have changed
  var mytextbox = getElem("id",id);
  var textfieldcontents = mytextbox.value;
  var mgBoxDiv = getElem("id","mgBox");
  if(textfieldcontents.length > 0) {
    // execute new query to fill listbox

    // first, show "updating..." indicator inside mgBox DIV
    //var mgBoxDiv = getElem("id","mgBox");
    mgBoxDiv.innerHTML = "<small>&nbsp;Searching for '"+escape(textfieldcontents)+"'...&nbsp;</small>";
    theMgBox.isOpenAndFilled = false;
    theMgBox.highlightedline = null;  // reset selection

    // make sure the box is displayed (this may be the first time!!!)
    theMgBox.showMgBox();

    // cancel any previous http requests that are still active
    if( theMgBox.httprequestactive ) {
      // cancel currently ongoing request
      var tempText = mgBoxDiv.innerHTML;
      this.httprequest.abort(); // see http://www.w3schools.com/dom/dom_http.asp
      //theMgBox.hideMgBox();
      mgBoxDiv.innerHTML = tempText;
      // creates empty results, so we simply create a new object instead...
      delete theMgBox.httprequest; // not sure whether we need to delete objects, we do it just in case...
      theMgBox.httprequest = createXMLHttpRequest(); // create a new one...
      theMgBox.httprequestactive = false;
    }
    if( !theMgBox.httprequestactive ) {
      theMgBox.httprequestactive = true;
      // build name of cgi, based on textbox id (less 2-digit suffix if present)
      var cgi_name = theMgBox.ASSIST_CGI_TEMPLATE.replace(/@/,
                      theMgBox.textboxid.removeTrailingDigitsIfPresent());
      theMgBox.httprequest.open("GET", cgi_name+URLEncode(textfieldcontents),true);
      theMgBox.httprequest.onreadystatechange=function() {

        // Ready States
        //    * 0: UNINITIALIZED open() has not been called yet
        //    * 1: LOADING Request not yet made (send() not called)
        //    * 2: LOADED Contact established with server and HTTP status code recieved
        //    * 3: INTERACTIVE In Mozilla, called multiple times while response is fetched - every 4096 bytes of response
        //    * 4: COMPLETED Response complete

        if (theMgBox.httprequest.readyState==4) {
        	// has the focus changed in the meantime?
          if (theMgBox.httprequestactive == false) {
            return;
          }

          //alert(theMgBox.httprequest.readyState.toString() + " - " + theMgBox.httprequest.responseText)
          var mgBoxDiv = getElem("id","mgBox");
          // store result in internal arrays
          var result_array = theMgBox.httprequest.responseText.split("\n");
          if(result_array[0]!="magic") {
            // error
            mgBoxDiv.innerHTML = "<small>&nbsp;No matches&nbsp;</small>";
          } else if(result_array.length<3) {
            // only magic, but no results
            mgBoxDiv.innerHTML = "<small>&nbsp;No matches&nbsp;</small>";
          } else {
            delete theMgBox.html_text_array;
            delete theMgBox.raw_text_array;
            delete theMgBox.id_array;
            theMgBox.html_text_array = new Array();
            theMgBox.raw_text_array = new Array();
            theMgBox.id_array = new Array();
            var total_html='<ul>';
            // file format is "html \n raw \n id \n [...]"
            for(var i=1; i<result_array.length-1; i+=3) { // ignore line after last \n
              theMgBox.html_text_array.push(result_array[i]);
              theMgBox.raw_text_array.push(result_array[i+1]);
              theMgBox.id_array.push(result_array[i+2]);
              total_html = total_html + '<li>' + result_array[i] + '</li>';
            }
            total_html = total_html + '</ul>'
            mgBoxDiv.innerHTML = total_html;
            // highlight the first line
            theMgBox.highlightedline = 0;
            mgBoxDiv.childNodes[0].childNodes[theMgBox.highlightedline].className="mgHighlighted";
            theMgBox.isOpenAndFilled = true;
          }
          theMgBox.httprequestactive = false;
        }
      }
      theMgBox.httprequest.send(null);   // this will send the request
    }
  } else {
    theMgBox.hideMgBox();
    mgBoxDiv.innerHTML = '';
  }
  // otherwise we do nothing
}
mgBox.prototype.timeoutReached = MgTimeoutReached;

function MgShowMgBox() {
  box = getElem("id","mgBox","");
  par = this.textfieldhandle;
  x = findPosX(par);
  y = findPosY(par);
  box.style.top=y+par.offsetHeight;
  box.style.left=x;
  box.style.visibility = "visible";
}
mgBox.prototype.showMgBox = MgShowMgBox;

function MgHideMgBox() {
  getElem("id","mgBox","").style.visibility = "hidden";
  theMgBox.isOpenAndFilled = false;
}
mgBox.prototype.hideMgBox = MgHideMgBox;

// iterate through all text fields, install handlers for
// all text fields with attribute "mgBox"
function MgInitMgBoxes() {

// This function was inspired by Mircho Mirev's cAutocomplete.autoInit()
//  mo /mo@momche.net/
//	Copyright (c) 2004 Mircho Mirev
//
  var nI = 0;
  var sLangAtt;
  var hTextField=null;

  var nInputsLength = document.getElementsByTagName( 'INPUT' ).length
  for( nI = 0; nI < nInputsLength; nI++ ) {
    if( document.getElementsByTagName( 'INPUT' )[ nI ].type.toLowerCase() == 'text' ) {
      sLangAtt = document.getElementsByTagName( 'INPUT' )[ nI ].getAttribute( 'mgBox' )
      if( sLangAtt != null && sLangAtt.length > 0 ) {
        //alert("i think i found one");
        hTextField=document.getElementsByTagName( 'INPUT' )[ nI ];
        //alert(hTextField.onfocus);
        hTextField.onfocus=theMgBox.onFocus;
        hTextField.onblur=theMgBox.onBlur;
        //alert(hTextField.onfocus);
      }
    }
  }

  // call hook if present
  if(typeof mgBox_onLoad_hook == 'function') {
    mgBox_onLoad_hook();
  }
}
mgBox.prototype.initMgBoxes = MgInitMgBoxes;


/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
////
////  the "main()" function
////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
theMgBox = new mgBox();

// Attach onLoad handler...
if( window.attachEvent ) {
  window.attachEvent( 'onload', theMgBox.initMgBoxes )
} else if( window.addEventListener ) {
  window.addEventListener( 'load', theMgBox.initMgBoxes, false )
}

