/// Ajax = Asynchronous JavaScript + XML (+ HTML)
/// Ajax framework for Internet Explorer (6.0, ...) and Firefox (1.0, ...)
/// by Matthias Hertel
/// More information on: http://ajaxaspects.blogspot.com/ and http://ajaxaspekte.blogspot.com/
/// -----
/// ajax.js: Common Javascript methods and global objects
/// 05.06.2005 creation.
/// 19.06.2005 minor corrections to webservices.
/// 25.06.2005 ajax action queue and timing.
/// 02.07.2005 queue up actions fixed.
/// 10.07.2005 ajax.timeout
/// 10.07.2005 a option object that is passed from ajax.Start() to prepare() is also queued.
/// 10.07.2005 a option object that is passed from ajax.Start() to prepare(), finish()
///            and onException() is also queued.
/// 12.07.2005 correct xml encoding when CallSoap()
/// 20.07.2005 more datatypes and XML Documents 
/// 20.07.2005 more datatypes and XML Documents fixed

// ----- global variable for the proxies to webservices. -----

/// <summary>The root object for the proxies to webservices.</summary>
var proxies = new Object();

proxies.current = null; // the current active webservice call.
proxies.xmlhttp = null; // The current active xmlhttp object.


// ----- global variable for the ajax engine. -----

/// <summary>The root object for the ajax engine.</summary>
var ajax = new Object();

ajax.current = null; /// The current active AJAX action.
ajax.option = null; /// The options for the current active AJAX action.

ajax.queue = new Array(); /// The pending AJAX actions.
ajax.options = new Array(); /// The options for the pending AJAX actions.

ajax.timer = null; /// The timer for delayed actions.


// ----- AJAX engine and actions implementation -----

///<summary>Start an AJAX action by entering it into the queue</summary>
ajax.Start = function (action, options) {

  if (action == null) {
    alert("ajax.Start: Argument action must be set.");
    return;

  } else if ((action.queueClear != null) && (action.queueClear == true)) {
    ajax.queue = new Array();
    ajax.options = new Array();

  } else if ((ajax.queue.length > 0) && ((action.queueMultiple == null) || (action.queueMultiple == false))) {
    // remove existing action entries from the queue and clear a running timer
    if ((ajax.timer != null) && (ajax.queue[0] == action)) {
      window.clearTimeout(ajax.timer);
      ajax.timer = null;
    } // if
    
    var n = 0;
    while (n < ajax.queue.length) {
      if (ajax.queue[n] == action)
        ajax.queue.splice(n, 1);
      else
        n++;
    } // while
  } // if
  
  if ((action.queueTop == null) || (action.queueTop == false)) {
    // to the end.
    ajax.queue.push(action);
    ajax.options.push(options);

  } else {
    // to the top
    ajax.queue.unshift(action);
    ajax.options.unshift(options);
  } // if

  // check if the action should start
  ajax.Next(false);
} // ajax.Start


///<summary>Check, if the next AJAX action can start.
///This is an internal method that should not be called from external.</summary>
ajax.Next = function (forceStart) {
  var ca = null // current action
  var co = null // current opptions
  var data = null;

  if (ajax.timer != null)
    return; // a call is pendig: wait more time

  if (ajax.queue.length == 0)
    return; // nothing to do.
  
  ca = ajax.queue[0];
  co = ajax.options[0];

  if (ajax.current != null) {
    if (ca.sync) {
      throw new Error(110,"Cannot call "+ca.call.service.url+"/"+ca.call.fname+".\nA call ("+ajax.current.call.service.url+"/"+ajax.current.call.fname+") is active");
    }
    else {
      return; // a call is active: wait more time
    }
  }

  if ((forceStart == true) || (ca.delay == null) || (ca.delay == 0)) {
    // start top action
    ajax.queue.shift();
    ajax.options.shift();
    ajax.current = ca;
    ajax.option = co;
    ajax.current.option = co;
    
    // get the data
    if (ca.prepare != null)
      data = ca.prepare(co);

    if (ca.call == null) {
      // no call
      ajax.Finsh(data);
    } else { 
      // start the call
      ca.call.sync = ca.sync;
      ca.call.func = ajax.Finsh;
      ca.call.onException = ajax.Exception;
      // multi-valued call
      if ((typeof(data)=="object") && (data!=null) && data.length) {
        ca.args = new Object();
        var aArg = new Array();
        for (var i=0; i<data.length; i++) {
          ca.args["a"+i] = data[i];
          aArg[aArg.length] = "a"+i;
        }
        with (ca.args) {
          eval("ca.call("+aArg.join(",")+")");
        }
      }
      // single-valued call
      else { 
        ca.call(data);
      }
      // start timeout timer
      if (ca.timeout != null)
        ajax.timer = window.setTimeout(ajax.Cancel, ca.timeout * 1000);
    } // if
    
  } else {
    // start a timer and wait
    ajax.timer = window.setTimeout(ajax.EndWait, ca.delay);
  } // if
} // ajax.Next


///<summary>The delay time of an action is over.</summary>
ajax.EndWait = function() {
  ajax.timer = null;
  ajax.Next(true);
} // ajax.EndWait


///<summary>The current action timed out.</summary>
ajax.Cancel = function() {
  proxies.cancel(); // cancel the current webservice call.
  ajax.timer = null;
  ajax.current = null;
  ajax.option = null;
  window.setTimeout(ajax.Next, 200); // give some to time to cancel the http connection.
  // ajax.Next(false);
} // ajax.Cancel


///<summary>Finish an AJAX Action the normal way</summary>
ajax.Finsh = function (data) {
  // clear timeout timer if set
  if (ajax.timer != null) {
    window.clearTimeout(ajax.timer);
    ajax.timer = null;
  } // if

  // reset the running action
  var current = ajax.current;
  var option = ajax.option;
  
  ajax.current = null;
  ajax.option = null;

  // use the data
  if (current) {
    if (current.finish != null)
      current.finish(data, option);
  }
  ajax.Next(false)
} // ajax.Finsh


///<summary>Finish an AJAX Action with an exception</summary>
ajax.Exception = function (ex) {
  // use the data
  if (ajax.current.onException != null)
    ajax.current.onException(ex, ajax.option);

  // reset the running action
  ajax.current = null;
  ajax.option = null;
} // ajax.Exception


///<summary>Clear all pending AJAX actions.</summary>
ajax.CancelAll = function () {
  // cancel the current action
  ajax.Cancel();
  // and clear the queue
  ajax.queue = new Array();
  ajax.options = new Array();
} // ajax.CancelAll


// ----- webservice proxy implementation -----

///<summary>Execute a soap call.
///Build the xml for the call of a soap method of a webservice
///and post it to the server.</summary>
proxies.callSoap = function (args) {
  var p = args.callee;
  var x = null;

  proxies.current = p;

  try {
    x = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) { }

  if (x == null) {
    try {
      x = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) { }
  } // if
  
  // Gecko / Mozilla / Firefox
  if ((x == null) && (typeof(XMLHttpRequest) != "undefined"))
    x = new XMLHttpRequest();

  proxies.xmlhttp = x;

  // envelope start
  var soap = "<?xml version='1.0' encoding='utf-8'?>"
    + "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>"
    + "<soap:Body>"
    + "<" + p.fname + " xmlns='" + p.service.ns + "'>";

  // parameters    
  for (n = 0; (n < p.params.length) && (n < args.length); n++) {
    var val = args[n];
    var typ = p.params[n].split(':');
    
    if ((typ.length == 1) || (typ[1] == "string")) {
      val = String(args[n]).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

    } else if (typ[1] == "int") {
      val = parseInt(args[n]);
    } else if (typ[1] == "float") {
      val = parseFloat(args[n]);

    } else if ((typ[1] == "x") && (typeof(args[n]) == "string")) {
      val = args[n];

    } else if ((typ[1] == "x") && (typeof(XMLSerializer) != "undefined")) {
      val = (new XMLSerializer()).serializeToString(args[n].firstChild);

    } else if (typ[1] == "x") {
      val = args[n].xml;

    } else if ((typ[1] == "bool") && (typeof(args[n]) == "string")) {
      val = args[n].toLowerCase();
      
    } else if (typ[1] == "bool") {
      val = String(args[n]).toLowerCase();

    } else if (typ[1] == "date") {
      // calculate the xml format for datetime objects from a javascript date object
      var s, ret;
      ret = String(val.getFullYear());
      ret += "-";
      s = String(val.getMonth() + 1);
      ret += (s.length == 1 ? "0" + s : s);
      ret += "-";
      s = String(val.getDate() + 1);
      ret += (s.length == 1 ? "0" + s : s);
      ret += "T";
      s = String(val.getHours() + 1);
      ret += (s.length == 1 ? "0" + s : s);
      ret += ":";
      s = String(val.getMinutes() + 1);
      ret += (s.length == 1 ? "0" + s : s);
      ret += ":";
      s = String(val.getSeconds() + 1);
      ret += (s.length == 1 ? "0" + s : s);
      val = ret;
    } // if
    soap += "<" + typ[0] + ">" + val + "</" + typ[0] + ">"
  } // for

  // envelope end
  soap += "</" + p.fname + ">"
    + "</soap:Body>"
    + "</soap:Envelope>";

  x.open("POST", p.service.url, ((p.func != null) && (!p.sync)));
  x.setRequestHeader("SOAPAction", p.action);
  x.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
  if ((window.oTop) && (typeof(window.oTop.oPage)=="object") && (typeof(window.oTop.oPage.oData)=="object") && (typeof(window.oTop.oPage.oData.sUser_Login)=="string") && window.oTop.oPage.oData.sUser_Login.length > 0) {
    x.setRequestHeader("sUser_Login", window.oTop.oPage.oData.sUser_Login);
  }

  if (p.corefunc != null) {
    // async call with xmlhttp-object as parameter
    x.onreadystatechange = p.corefunc;
    x.send(soap);

  } else if ((p.func != null) && (!p.sync)) {
    // async call
    x.onreadystatechange = proxies.response;
    x.send(soap);

  } else {
    // sync call
    x.send(soap);
    return(proxies.response());
  } // if
} // proxies.callSoap


// cancel the running webservice call.
proxies.cancel = function() {
  var cc = proxies.current;
  var cx = proxies.xmlhttp;
  
  if (proxies.xmlhttp != null) {
    proxies.xmlhttp.onreadystatechange = function() { };
    proxies.xmlhttp.abort();
    if (proxies.current.onException != null)
      proxies.current.onException("WebService call was canceled.")
    proxies.current = null;
    proxies.xmlhttp = null;
  } // if
} // proxies.cancel


///<summary>Callback method for a webservice call that dispatches the response to servive.func or service.onException.</summary>
proxies.response = function () {
  var ret = null;
  var x = proxies.xmlhttp;
  var rtype = (proxies.current.rtype.length>0)?proxies.current.rtype[0].split(':'):null;

  if ((x != null) && (x.readyState == 4)) {
    if (x.status == 200) {

      if (rtype==null) {
        ret = null;
      }
      else {
        var xNode = x.responseXML.getElementsByTagName(rtype[0])[0];

        if ((xNode == null) || (xNode.firstChild == null)) {
          ret = null;
        } else if ((rtype.length == 1) || (rtype[1] == "string")) {
          ret = (xNode.textContent ? xNode.textContent : xNode.text);

        } else if (rtype[1] == "bool") {
          ret = ((xNode.textContent ? xNode.textContent : xNode.text).toLowercase() == "true");

        } else if (rtype[1] == "int") {
          ret = parseInt(xNode.textContent ? xNode.textContent : xNode.text);

        } else if (rtype[1] == "float") {
          ret = parseFloat(xNode.textContent ? xNode.textContent : xNode.text);

        } else if ((rtype[1] == "x") && (typeof(XMLSerializer) != "undefined")) {
          ret = (new XMLSerializer()).serializeToString(xNode.firstChild);
          ret = ajax._getXMLDOM(ret);

        } else if (rtype[1] == "x") {
          ret = xNode.firstChild.xml;
          ret = ajax._getXMLDOM(ret);

        } else  {
          ret = (xNode.textContent ? xNode.textContent : xNode.text);
        } // if
      }
      
      if (proxies.current.func == null) {
        if (rtype==null) // sync
          return
        else
          return ret;
      }
      else {
        if (rtype==null) // async
          proxies.current.func()
        else
          proxies.current.func(ret);
      }

    } else if (proxies.current.onException == null) {
       // no exception

    } else {
      // raise an exception 
      ret = new Error();

      if (x.status == 404) {
        ret.message = "The webservice could not be found.";

      } else if (x.status == 500) {
        ret.name = "SoapException";
        try {
          var n = x.responseXML.documentElement.firstChild.firstChild.firstChild;
          while (n != null) {
            if (n.nodeName == "faultcode") ret.message = n.firstChild.nodeValue;
            if (n.nodeName == "faultstring") ret.description = n.firstChild.nodeValue;
            n = n.nextSibling;
          } // while
        }
        catch(e) {
          alert(x.responseText);
          ret.message = "Server Error";
          return ret;
        }
   
      } else if ((x.status == 502) || (x.status == 12031)) {
        ret.message = "The server could not be found.";

      } else {
        // no classified response.
        ret.message = "Result-Status:" + x.status + "\n" + x.responseText;
      } // if
      proxies.current.onException(ret);
    } // if
    
    proxies.xmlhttp = null;
    proxies.current = null;
  } // if
} // proxies.response


///<summary>Callback method to show the result of a soap call in an alert box.</summary>
///<remarks>To set up a debug output in an alert box use:
///proxies.service.method.corefunc = proxies.alertResult;</remarks>
proxies.alertResult = function () {
  var x = proxies.xmlhttp;
  
  if (x.readyState == 4) {
    if (x.status == 200) {
     if (x.responseXML.documentElement.firstChild.firstChild.firstChild == null)
       alert("(no result)");
     else
       alert(x.responseXML.documentElement.firstChild.firstChild.firstChild.firstChild.nodeValue);

    } else if (x.status == 404) { alert("Error!\n\nThe webservice could not be found.");

    } else if (x.status == 500) {
      // a SoapException
      var ex = new Error();
      ex.name = "SoapException";
      var n = x.responseXML.documentElement.firstChild.firstChild.firstChild;
      while (n != null) {
        if (n.nodeName == "faultcode") ex.message = n.firstChild.nodeValue;
        if (n.nodeName == "faultstring") ex.description = n.firstChild.nodeValue;
        n = n.nextSibling;
      } // while
      alert("The server threw an exception.\n\n" + ex.message + "\n\n" + ex.description);
    
    } else if (x.status == 502) { alert("Error!\n\nThe server could not be found.");

    } else {
      // no classified response.
      alert("Result-Status:" + x.status + "\n" + x.responseText);
    } // if
    
    proxies.xmlhttp = null;
    proxies.current = null;
  } // if
} // proxies.alertResult


///<summary>Show all the details of the returned data of a webservice call.
///Use this method for debugging transmission problems.</summary>
///<remarks>To set up a debug output in an alert box use:
///proxies.service.method.corefunc = proxies.alertResponseText;</remarks>
proxies.alertResponseText = function () {
 if (proxies.xmlhttp.readyState == 4)
   alert("Status:" + proxies.xmlhttp.status + "\nRESULT:" + proxies.xmlhttp.responseText);
} // proxies.alertResponseText


///<summary>show the details about an exception.</summary>
proxies.alertException = function(ex) {
  var s = "Exception:\n\n";

  if (ex.constructor == String) {
    s = ex;
  } else {
    if ((ex.name != null) && (ex.name != ""))
      s += "Type: " + ex.name + "\n\n";
      
    if ((ex.message != null) && (ex.message != ""))
      s += "Message:\n" + ex.message + "\n\n";

    if ((ex.description != null) && (ex.description != "") && (ex.message != ex.description))
      s += "Description:\n" + ex.description + "\n\n";
  } // if

  ajax.current = null;
  ajax.option = null;

  var sMsg = ex.description;
  var iDescriptionStart = sMsg.indexOf("<Message>") + 9;
  var iDescriptionEnd = sMsg.indexOf("</Message>");
  
  if (iDescriptionEnd>iDescriptionStart) {
    ex.Message = sMsg.substr(iDescriptionStart, iDescriptionEnd-iDescriptionStart);

    var iDialogStart = sMsg.indexOf("<EventLevel>") + 12;
    var iDialogEnd = sMsg.indexOf("</EventLevel>");
    if (iDialogEnd>iDialogStart) {
      ex.EventLevel = parseInt(sMsg.substr(iDialogStart, iDialogEnd-iDialogStart),10);
    }
  }
  
  throw ex;
} // proxies.alertException


///<summary>Get a browser specific implementation of the XMLDOM object, containing a XML document.</summary>
///<param name="xmlText">the xml document as string.</param>
ajax._getXMLDOM = function (xmlText) {
  var obj = null;

  if ((document.implementation != null) && (typeof document.implementation.createDocument == "function")) {
    // Gecko / Mozilla / Firefox
    var parser = new DOMParser();
    obj = parser.parseFromString(xmlText, "text/xml");

  } else {    
    // IE
    try {
      obj = new ActiveXObject("MSXML2.DOMDocument");
    } catch (e) { }

    if (obj == null) {
      try {
        obj = new ActiveXObject("Microsoft.XMLDOM");
      } catch (e) { }
    } // if
  
    if (obj != null) {
      obj.async = false;
      obj.validateOnParse = false;
    } // if
    obj.loadXML(xmlText);
  } // if
  return(obj);
} // _getXMLDOM


///<summary>show the details of a javascript object.</summary> 
///<remarks>This helps a lot while developing and debugging.</remarks> 
function inspectObj(obj) {
  var s = "InspectObj:";
  
  if (obj.constructor == String)
    s = "\"" + obj + "\"";
  else if (obj.constructor == Array)
    s += " _ARRAY";
  else if (typeof(obj) == "function")
    s += " [function]" + obj;
      
  for (p in obj) {
    try {
      if (obj[p] == null) {
        s += "\n" + String(p) + " (...)";

      } else if (typeof(obj[p]) == "function") {
        s += "\n" + String(p) + " [function]";

      } else if (obj[p].constructor == Array) {
        s += "\n" + String(p) + " [ARRAY]: " + obj[p];
        for (n = 0; n < obj[p].length; n++)
          s += "\n  " + n + ": " + obj[p][n];

      } else {
        s += "\n" + String(p) + " [" + typeof(obj[p]) + "]: " + obj[p];
      } // if
    } catch (e) { s+= e;}
  } // for
  alert(s);
} // inspectObj

// ----- End -----
