// $Id: ajax.js,v 1.26 2007/08/07 18:22:54 cm rlse $ 

/******************************************************************

  MODULE: Framework

  FOR:  National Library of Canada
  BY:   ...
  DATE: ...

  Provides comment functions to perform ajax calls

  USAGE:
  see framework demo
  
  REVISIONS:
  2000-feb-03, 11:00,  awf
  Updated ajaxGetSnippet() to take an optional data string in the format
  'key1:value1;key2:value2' to be available on the server side in a
  hashed array $data, such that $data['key1] = 'value1', ...
  
  2000-feb-07, 16:30,  awf
  Added a helper createHttpRequest() to run through the various types
  of methods to create an XmlHttpRequest, also added  a 
  callServerSideFunction() which allows for custom callbacks
  
  2000-feb-13, 12:00,  awf
  Added a third type (id=3) ajaxGetSnippetHandler to put new snippets at the end
  of the *list* of added items instead of at the start (where id=2).
  I did not see constants in use, so I left it as a numeric 3
  
  2006-mar-16, 12:00, awf
  Added empty() to the framework
  
  2006-mar-29, 16:00
  Encoding ajax calls with urlencode(), so we needed an urldecode() on the
  javascript side
  
  2006-nov-21, 14:30
  Updated the on onreadystatechange to put the function call on the outside of the eval not the inside
  to work properly in IE
  
  2006-dec-08, 15:15, awf
  If an ajax function returns an entire document (i.e. <!DOCTYPE blah blah) then the
  callServerSideFunction will replace the entire document body with the returned text.
  This is generic (so any ajax could potentially return the entire page), but was added
  to support capturing ajax errors in a *Bug Report* page
  Asynchronous calls, will need to explicitly call handleAjaxServerErrors() this can be 
  fixed so that even asynchronous need not make the call... I may implement that change
  if needed
  
  2006-dec-28, 13:00, awf
  Bug0001306: Implement AJAX call to update result set prior to printing barcode
  Updated ajaxUpdateSet() to be synchronous
  
******************************************************************/


// Based on the various browsers, the XmlHttpRequest object
// is instantiated in many ways, this function
// hides <i>how</i> the request is created and simply
// gives it to you
function createHttpRequest()
{
  var httpRequest = false;

  var isMoziallaAndSafari = window.XMLHttpRequest;
  var isInternetExplorer = window.ActiveXObject;
  
  if (isMoziallaAndSafari) 
  { 
    httpRequest = new XMLHttpRequest();
    if (httpRequest.overrideMimeType) 
    {
      httpRequest.overrideMimeType('text/xml');
    }
  } 
  else if (isInternetExplorer) 
  { 
    try 
    {
      httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    } 
    catch (e) 
    {
      try 
      {
        httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
      } 
      catch (e) 
      {}
    }
  }
  return httpRequest;
}

// A generic means to display an ajax error
function showXmlHttpRequestError(httpRequest)
{
  error('XmlHttpRequest Response, request status => '+ httpRequest.status);    
  error("There was a problem retrieving the XML data:\n" + httpRequest.statusText);
  error(httpRequest.responseText);
}

function callServerSideFunction(_phpFileUrl,_processResultsFunctionText,_async)
{
  if (_async == undefined)
  {
  	_async = true;
  }

  var httpRequest = createHttpRequest();

  if (!httpRequest) 
  {
    error('Cannot create an XmlHttp instance');
    return false;
  }


  httpRequest.onreadystatechange = function() {
   switch(httpRequest.readyState) {
        case 0: // Request is uninitialized.
        case 1: // Request has not been sent.
        case 2: // Request has been sent and is being processed.
        case 3: // Request is being processed.
        break;

        case 4: // Request is complete.
          if(httpRequest.status == 200) { // No Errors
            eval(_processResultsFunctionText);
          } else {
            error('Ajax Failed: ' + httpRequest.status);
          }
        break;
      }
    }

    _phpFileUrlParts = _phpFileUrl.split('?');
    var uri = noCache();
    uri = _phpFileUrlParts[0] + uri.replace('&', '?');
    var queryString = _phpFileUrlParts[1];

    httpRequest.open('POST', uri, _async);
    httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    httpRequest.send(queryString);
/*
		 httpRequest.onreadystatechange = function() { eval(_processResultsFunctionText) };
		 httpRequest.open('GET', _phpFileUrl + noCache() , _async);
		 httpRequest.send(null);
*/
  if (!_async)
  {
  	return handleAjaxServerErrors(httpRequest) ? urlDecode(httpRequest.responseText) : "SERVER_ERROR";
  }
}


// IE caches ajax requests.  Put a random string on the end of every request to ensure it is unique
function noCache()
{
  return '&' + Math.random() * 10000 + '=noCache';
}


function handleAjaxServerErrors(httpRequest)
{
  var response = httpRequest.responseText;
  var hasServerErrors = response.indexOf("<!DOCTYPE") == 0;
  var isServerOkay = !hasServerErrors;
  if (hasServerErrors)
  {
    body = document.getElementsByTagName('body')[0];
    body.innerHTML = response;
  }
  return isServerOkay;
}


/*********************************************
** loadXMLDoc
** _url - url of ajax request
** _async - true or false.  Asynchronous or synchronous
*********************************************/
function loadXMLDoc(_url, _async) 
{

  // default ajax requests to asynchronous.
  if (_async == undefined) _async = true;

  debug('XMLHttpRequest open/send => '+_url);    
  http_request = createHttpRequest();

  if (!http_request) 
  {
    error('Cannot create an XMLHTTP instance');
    return false;
  }
  
  http_request.onreadystatechange = processReqChange;
  http_request.open('POST', _url + noCache(), _async);
  http_request.send(null);
}

/*********************************************
** processReqChange
*********************************************/
function processReqChange()
{
  // only if req shows "complete"
  if (http_request.readyState == 4) 
  {

    // only if "OK"
    if (http_request.status == 200) 
    {

      debug(http_request.responseText);

      response  = http_request.responseXML.documentElement;
      method = getXmlData(response.getElementsByTagName('method')[0]);
      result = response.getElementsByTagName('result')[0];

      debug('XMLHttpRequest Invoke => '+method);    
      
      eval(method + '(result)');
      
    } 
    else 
    {
      showXmlHttpRequestError(http_request);
    }   
  }
}


/*
 *  AJAX Handlers
 */

/*********************************************
** ajaxPopStack
*********************************************/
function ajaxPopStack(_input)
{
  url = 'index.php?fuseaction=ajax.get-close&stack-key='+_input;     
  loadXMLDoc(url);
}

/*********************************************
** ajaxPopStackHandler
*********************************************/
function ajaxPopStackHandler(_result)
{
  strURL = new String(trim(getXmlData(_result.getElementsByTagName('url')[0])));   
  strURL = strURL.replace(/&amp;/g,'&');
  info('Redirect to '+strURL);
  top.location.href = strURL;
  return;
}

/*********************************************
 ajaxGetSnippet

 This method can now take data in the form of
 
 key1:value1;key2:value2;
 which will be available on the server as
 $hashtable[key1] => value1
 $hashtable[key2] => value2
 
*********************************************/

function ajaxGetSnippet(_circuit, _input, _parent, _type, _data)
{
  _data = !_data ? '' : _data;
  url = 'index.php?fuseaction=ajax.get-snippet&snippet-circuit='+_circuit+'&snippet-file='+_input+'&snippet-parent='+_parent+'&snippet-type='+_type+'&snippet-data='+_data;     
  loadXMLDoc(url);
}


/*********************************************
** ajaxGetSnippetHandler
*********************************************/
function ajaxGetSnippetHandler(_result)
{
  strElement = new String(trim(getXmlData(_result.getElementsByTagName('snippet')[0])));   
  strPar = new String(trim(getXmlData(_result.getElementsByTagName('parent')[0])));   
  strType = new String(trim(getXmlData(_result.getElementsByTagName('type')[0])));   

  var _insPar = document.getElementById(strPar);

  if (strType == 1) {

    /* Add Table Row */
    var _lastRow = _insPar.rows.length;
    var _newRow = _insPar.insertRow(_lastRow);

    _tmp=strElement;
    _posn = _tmp.indexOf("</td>");   
    while (_posn > 0) {
      _posn = _tmp.indexOf("</td>");
      _row=_tmp.substring(0,_posn+5);
      _tmp=_tmp.substring(_posn+5);
      if (_posn > 0) {     
        _Cell = document.createElement("TD");
        _Cell.innerHTML = _row;
        _newRow.appendChild(_Cell);
      }
    }
	} 
	else if (strType == 2)
	{
    var _span = document.createElement("span");
    _span.innerHTML = strElement;
    
    if (_insPar.nextSibling)
    {
      _insPar.parentNode.insertBefore(_span,_insPar.nextSibling);    
    }
    else
    {
      _insPar.parentNode.appendChild(_span);
		}
	}
	else
	{
    var span = document.createElement("span");
    span.innerHTML = strElement;
    _insPar.parentNode.appendChild(span);
	}
  return;
}


var cnt=0;


/*********************************************
** ajaxCheckAll
*********************************************/
function ajaxCheckAll(_input,_mode)
{
  url = 'index.php?fuseaction=ajax.check-all&set-id='+_input+'&mode='+_mode;  
  loadXMLDoc(url); 
  //var request = callServerSideFunction(url,'ajaxCheckAllHandler',false);
  //debug('Response ' + request);  
}

/*********************************************
** ajaxCheckAllHandler
*********************************************/
function ajaxCheckAllHandler(_result)
{
  //strSet = new String(trim(getXmlData(_result.getElementsByTagName('count')[0])));   
  //info('Checked Records '+ strSet);
  enableScreen();
  return;
}

/*********************************************
** ajaxUpdateSet
*********************************************/
function ajaxUpdateSet(_set,_page,_update,_row,_key )
{
  url  = 'index.php?fuseaction=ajax.update-set&set-id='+_set+'&page-no='+_page;
  url += '&update-checks='+_update+'&row='+_row+'&primary-key='+_key;
  loadXMLDoc(url,false); 
}

/*********************************************
** ajaxUpdateSetHandler
*********************************************/
function ajaxUpdateSetHandler(_result)
{
  //strSet = new String(trim(getXmlData(_result.getElementsByTagName('count')[0])));   
  //info('Checked Records '+ strSet);
  enableScreen();
  return;
}

/*********************************************
** ajaxUpdateChecks
*********************************************/
function ajaxUpdateChecks(_set,_old,_new )
{
  url  = 'index.php?fuseaction=ajax.update-checks&set-id='+_set;
  url += '&row='+_old+'&primary-key='+_new;
  loadXMLDoc(url,false); 
}

/*********************************************
** ajaxUpdateChecksHandler
*********************************************/
function ajaxUpdateChecksHandler(_result)
{
  //strSet = new String(trim(getXmlData(_result.getElementsByTagName('count')[0])));   
  //info('Checked Records '+ strSet);
  enableScreen();
  return;
}

/*********************************************
** ajaxFilterDropdown
*********************************************/
function ajaxFilterDropdown(_targetId, _sourceId, _circuit, _function)
{

  // _targetId is the dropdown that will be updated with selected options
  // _sourceId is the dropdown/object that will trigger the filtering of the target dropdown
  // _circuit is the circuit (location) of the function that get the options
  // _function is the function that gets the options

  // get the value of the source object
  var sourceObj = document.getElementById(_sourceId);
  sourceValue = sourceObj.options[sourceObj.selectedIndex].value;

  // info(_targetId + ': sourceValue:' + sourceValue);
 
  // if there is not source value then do not perform ajax call, just update target dropdown to no options available
  if (sourceValue == '')
  {

    // remove existing options and disable target dropdown
    target = document.getElementById(_targetId);
    target.innerHTML = '';
    // target.disabled = true;

    var opt = document.createElement('OPTION');
    opt.value = '';  // blank means no option selected
    opt.text = 'No Options Available';  // TODO translate string

    target.options.add(opt, i);

    // the source has been updated, so filter the target and cascade
    // TODO: fire event only works in IE
    target.fireEvent('onchange');

  // if there is a source value then perform ajax call to get options
  } 
  else 
  {
    // PROGRAMMERS NOTE: Solved IE problem of scroll wheel firing onChange event and overloading the server with ajax calls
    // Delay getting the options by .5 seconds so we do not fire off too many ajax requests at the server
    var t = ++cnt;
    setTimeout('if (' + t + ' == cnt) getOptions("' + _targetId + '","' + sourceValue + '","' + _circuit + '","' + _function + '");', 500);
  }

}

// function that makes AJAX call to get options for dropdown
function getOptions(_targetId, _sourceValue, _circuit, _function)
{
  url = 'index.php?fuseaction=ajax.get-filter&targetId=' + _targetId + '&sourceValue=' + _sourceValue + '&circuit=' + _circuit + '&function=' + _function;     
  loadXMLDoc(url); 
}

/*********************************************
** ajaxFilterDropdown
*********************************************/
function ajaxFilterDropdownHandler(_result)
{

  // get target object
  var targetId = _result.getElementsByTagName('targetId');
  var target = document.getElementById(getXmlData(targetId[0]));
  target.innerHTML = '';

  // get list of options for xml response and create new option elements
  var options = _result.getElementsByTagName('option');
  var optionLength = options.length;
  // if (optionLength > 1) {
  //  target.disabled = false;
  // } else {
  //  target.disabled = true;
  // }

  // go throught each returned option and create new options for target dropdown
  for (i=0; i < optionLength; i++)
  {
    var opt = document.createElement('OPTION');
    opt.value = options[i].getAttribute('value');
    opt.text = getXmlData(options[i]);
    target.options.add(opt, i);
  }

  // the source has been updated, so filter the target and cascade
  // TODO: fire event only works in IE
  target.fireEvent('onchange');

}

/*********************************************
** getXmlData
*********************************************/
function getXmlData(_element)
{
  if (_element.firstChild) return _element.firstChild.data;
  else                     return '';
}

/*********************************************
** getXmlData
*********************************************/
function getXmlAttr(_element, _attribute)
{
  return eval('_element.firstChild.'+_attribute);
}

/*********************************************
** Decode an url encoded string
*********************************************/
function urlDecode(encoded)
{
  var decoded = "";
  var i = 0;
  while (i < encoded.length)
  {
    var currentCharacter = encoded.charAt(i);
    var isSpace = currentCharacter == "+";
    var isHex = currentCharacter == "%" && isHexCharacter(encoded,i+1) && isHexCharacter(encoded,i+2);
    
	if (isHex) 
    {
       decoded += unescape(encoded.substr(i,3));
       i += 3;
    } 
    else if (isSpace) 
	{
	  decoded += " ";
      i++;
    }
    else 
    {
      decoded += currentCharacter;
      i++;
    }
  }
  return decoded;
}

/*********************************************
** HEX characters are 0-9 a-f
*********************************************/
function isHexCharacter(text,index)
{
  var allHexCharacters = "0123456789ABCDEFabcdef"; 
  if (index >= text.length)
  {
    return false;
  }
  return allHexCharacters.indexOf(text.charAt(index)) != -1;
}



/*********************************************
** When doing synchronous ajax, you need an empty function
** that does nothing
*********************************************/
function empty()
{}

