// ===================================================================
// Author: Matt Kruse <matt@ajaxtoolbox.com>
// WWW: http://www.AjaxToolbox.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

// Erweiterungen BL: req.terminateInternal(), req.abort(), req.active

/**
 * The AjaxRequest class is a wrapper for the XMLHttpRequest objects which 
 * are available in most modern browsers. It simplifies the interfaces for
 * making Ajax requests, adds commonly-used convenience methods, and makes 
 * the process of handling state changes more intuitive.
 * An object may be instantiated and used, or the Class methods may be used 
 * which internally create an AjaxRequest object.
 */

function defined(x)
{
  return !(typeof(x) == 'undefined');
}

function isFunction(x)
{
  return (typeof(x) == 'function');
}
 
function AjaxRequest() {
  var req = new Object();
  
  // -------------------
  // Instance properties
  // -------------------

  /**
   * Timeout period (in ms) until an async request will be aborted, and
   * the onTimeout function will be called
   */
  req.timeout = null;
  
  /**
   *  Since some browsers cache GET requests via XMLHttpRequest, an
   * additional parameter called AjaxRequestUniqueId will be added to
   * the request URI with a unique numeric value appended so that the requested
   * URL will not be cached.
   */
  req.generateUniqueUrl = true;
  
  /**
   * The url that the request will be made to, which defaults to the current 
   * url of the window
   */
  req.url = window.location.href;
  
  /**
   * The method of the request, either GET (default), POST, or HEAD
   */
  req.method = "GET";
  
  /**
   * Whether or not the request will be asynchronous. In general, synchronous 
   * requests should not be used so this should rarely be changed from true
   */
  req.async = true;
  
  /**
   * The username used to access the URL
   */
  req.username = null;
  
  /**
   * The password used to access the URL
   */
  req.password = null;
  
  /**
   * The parameters is an object holding name/value pairs which will be 
   * added to the url for a GET request or the request content for a POST request
   */
  req.parameters = new Object();
  
  /**
   * The sequential index number of this request, updated internally
   */
  req.requestIndex = AjaxRequest.numAjaxRequests++;
  
  /**
   * Indicates whether a response has been received yet from the server
   */
  req.responseReceived = false;
  
  /**
   * The name of the group that this request belongs to, for activity 
   * monitoring purposes
   */
  req.groupName = null;
  
  /**
   * The query string to be added to the end of a GET request, in proper 
   * URIEncoded format
   */
  req.queryString = "";
  
  /**
   * After a response has been received, this will hold the text contents of 
   * the response - even in case of error
   */
  req.responseText = null;
  
  /**
   * After a response has been received, this will hold the XML content
   */
  req.responseXML = null;
  
  /**
   * After a response has been received, this will hold the status code of 
   * the response as returned by the server.
   */
  req.status = null;
  
  /**
   * After a response has been received, this will hold the text description 
   * of the response code
   */
  req.statusText = null;

  /**
   * An internal flag to indicate whether the request has been aborted
   */
  req.aborted = false;
  
  /**
   * An internal flag to indicate whether the request is active
   */
  req.active = false;
  
  /**
   * The XMLHttpRequest object used internally
   */
  req.xmlHttpRequest = null;

  // --------------
  // Event handlers
  // --------------
  
  /**
   * If a timeout period is set, and it is reached before a response is 
   * received, a function reference assigned to onTimeout will be called
   */
  req.onTimeout = null; 
  
  /**
   * A function reference assigned will be called when the request is 
         * aborted by the abort() function
   */
  req.onAbort = null;
        
  /**
   * A function reference assigned will be called when readyState=1
   */
  req.onLoading = null;

  /**
   * A function reference assigned will be called when readyState=2
   */
  req.onLoaded = null;

  /**
   * A function reference assigned will be called when readyState=3
   */
  req.onInteractive = null;

  /**
   * A function reference assigned will be called when readyState=4
   */
  req.onComplete = null;

  /**
   * A function reference assigned will be called after onComplete, if 
   * the statusCode=200
   */
  req.onSuccess = null;

  /**
   * A function reference assigned will be called after onComplete, if 
   * the statusCode != 200
   */
  req.onError = null;
  
  /**
   * If this request has a group name, this function reference will be called 
   * and passed the group name if this is the first request in the group to 
   * become active
   */
  req.onGroupBegin = null;

  /**
   * If this request has a group name, and this request is the last request 
   * in the group to complete, this function reference will be called
   */
  req.onGroupEnd = null;

  // Get the XMLHttpRequest object itself
  req.xmlHttpRequest = AjaxRequest.getXmlHttpRequest();
  if (req.xmlHttpRequest==null) { return null; }
  
  // -------------------------------------------------------
  // Attach the event handlers for the XMLHttpRequest object
  // -------------------------------------------------------
  req.xmlHttpRequest.onreadystatechange = 
  function() {
    if (req==null || req.xmlHttpRequest==null) { return; }
    if (req.xmlHttpRequest.readyState==1) { req.onLoadingInternal(req); }
    if (req.xmlHttpRequest.readyState==2) { req.onLoadedInternal(req); }
    if (req.xmlHttpRequest.readyState==3) { req.onInteractiveInternal(req); }
    if (req.xmlHttpRequest.readyState==4) { req.onCompleteInternal(req); }
  };
        
        
  // -------------------------------------------------------
  // Internal function to abort the request
  // -------------------------------------------------------
  req.terminateInternal =
  function() {
    if (req=null || req.xmlHttpRequest==null || !req.onCompleteInternalHandled || !req.active) { return; }
    req.active = false;
    AjaxRequest.numActiveAjaxRequests--;
    if (AjaxRequest.numActiveAjaxRequests==0 && isFunction(window['AjaxRequestEnd'])) {
      AjaxRequestEnd(req.groupName);
    }
    if (req.groupName!=null) {
      AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
      if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && isFunction(req.onGroupEnd)) {
        req.onGroupEnd(req.groupName);
      }
    }
    // Opera won't fire onreadystatechange after abort, but other browsers do. 
    // So we can't rely on the onreadystate function getting called. Clean up here!
    if (defined(req.xmlHttpRequest))
    {
      if (defined(req.xmlHttpRequest['onreadystatechange']))
        delete req.xmlHttpRequest['onreadystatechange'];
      req.xmlHttpRequest['onreadystatechange'] = null;
      delete req.xmlHttpRequest;
    }
    req.xmlHttpRequest = null;
  };
  
  // ---------------------------------------------------------------------------
  // Internal event handlers that fire, and in turn fire the user event handlers
  // ---------------------------------------------------------------------------
  // Flags to keep track if each event has been handled, in case of 
  // multiple calls (some browsers may call the onreadystatechange 
  // multiple times for the same state)
  req.onLoadingInternalHandled = false;
  req.onLoadedInternalHandled = false;
  req.onInteractiveInternalHandled = false;
  req.onCompleteInternalHandled = false;
  req.onLoadingInternal = 
    function() {
      if (req.onLoadingInternalHandled) { return; }
                        req.active = true;
      AjaxRequest.numActiveAjaxRequests++;
      if (AjaxRequest.numActiveAjaxRequests==1 && isFunction(window['AjaxRequestBegin'])) {
        AjaxRequestBegin();
      }
      if (req.groupName!=null) {
        if (!defined(AjaxRequest.numActiveAjaxGroupRequests[req.groupName])) {
          AjaxRequest.numActiveAjaxGroupRequests[req.groupName] = 0;
        }
        AjaxRequest.numActiveAjaxGroupRequests[req.groupName]++;
        if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==1 && isFunction(req.onGroupBegin)) {
          req.onGroupBegin(req.groupName);
        }
      }
      if (isFunction(req.onLoading)) {
        req.onLoading(req);
      }
      req.onLoadingInternalHandled = true;
    };
  req.onLoadedInternal = 
    function() {
      if (req.onLoadedInternalHandled) { return; }
      if (isFunction(req.onLoaded)) {
        req.onLoaded(req);
      }
      req.onLoadedInternalHandled = true;
    };
  req.onInteractiveInternal = 
    function() {
      if (req.onInteractiveInternalHandled) { return; }
      if (isFunction(req.onInteractive)) {
        req.onInteractive(req);
      }
      req.onInteractiveInternalHandled = true;
    };
  req.onCompleteInternal = 
    function() {
      if (req.onCompleteInternalHandled || req.aborted) { return; }
      req.onCompleteInternalHandled = true;
      req.responseReceived = true;
      req.status = req.xmlHttpRequest.status;
      req.statusText = req.xmlHttpRequest.statusText + '';
      if (req.xmlHttpRequest.responseText)
        req.responseText = req.xmlHttpRequest.responseText + '';
      else
        req.responseText = '';
      if (req.xmlHttpRequest.responseXML)
        req.responseXML = req.xmlHttpRequest.responseXML + '';
      else
        req.responseXML = '';

      if (isFunction(req.onComplete)) {
        req.onComplete(req);
      }
      if (req.xmlHttpRequest.status>=200 && req.xmlHttpRequest.status < 300 && isFunction(req.onSuccess)) {
        req.onSuccess(req);
      }
      else if (isFunction(req.onError)) {
        req.onError(req);
      }
      req.terminateInternal();
    };
  req.onTimeoutInternal = 
    function() {
      if (req==null || req.xmlHttpRequest==null || req.onCompleteInternalHandled) { return; }
              req.aborted = true;
              req.xmlHttpRequest.abort();
              if (isFunction(req.onTimeout)) {
                req.onTimeout(req);
              }
                        req.terminateInternal();
    };

  // ----------------
  // Instance methods
  // ----------------
  /**
   * The process method is called to actually make the request. It builds the
   * querystring for GET requests (the content for POST requests), sets the
   * appropriate headers if necessary, and calls the 
   * XMLHttpRequest.send() method
  */
  req.process = 
    function() {
      if (req.xmlHttpRequest!=null) {
        // Some logic to get the real request URL
        if (req.generateUniqueUrl && req.method=="GET") {
          req.parameters["AjaxRequestUniqueId"] = new Date().getTime() + "" + req.requestIndex;
        }
        var content = null; // For POST requests, to hold query string
        for (var i in req.parameters) {
          if (req.queryString.length>0) { req.queryString += "&"; }
          req.queryString += encodeURIComponent(i) + "=" + encodeURIComponent(req.parameters[i]);
        }
        if (req.method=="GET") {
          if (req.queryString.length>0) {
            req.url += ((req.url.indexOf("?")>-1)?"&":"?") + req.queryString;
          }
        }
        req.xmlHttpRequest.open(req.method,req.url,req.async,req.username,req.password);
        if (req.method=="POST") {
          if (typeof(req.xmlHttpRequest.setRequestHeader) == 'function') {
            req.xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
          }
          content = req.queryString;
        }
        if (req.timeout>0) {
          setTimeout(req.onTimeoutInternal,req.timeout);
        }
        req.xmlHttpRequest.send(content);
      }
    };

  /**
   * The abort method can be called to abort the request
  */
        req.abort = 
                function() {
      if (req==null || req.xmlHttpRequest==null || req.onCompleteInternalHandled) { return; }
              req.aborted = true;
              req.xmlHttpRequest.abort();
              if (isFunction(req.onAbort)) {
                req.onAbort(req);
              }
                        req.terminateInternal();
                };
        
        
  /**
   * An internal function to handle an Object argument, which may contain
   * either AjaxRequest field values or parameter name/values
   */
  req.handleArguments = 
    function(args) {
      for (var i in args) {
        // If the AjaxRequest object doesn't have a property which was passed, treat it as a url parameter
        if (!defined(req[i])) {
          req.parameters[i] = args[i];
        }
        else {
          req[i] = args[i];
        }
      }
    };

  /**
   * Returns the results of XMLHttpRequest.getAllResponseHeaders().
   * Only available after a response has been returned
   */
  req.getAllResponseHeaders =
    function() {
      if (req.xmlHttpRequest!=null) {
        if (req.responseReceived) {
          return req.xmlHttpRequest.getAllResponseHeaders();
        }
        alert("Cannot getAllResponseHeaders because a response has not yet been received");
      }
    };

  /**
   * Returns the the value of a response header as returned by 
   * XMLHttpRequest,getResponseHeader().
   * Only available after a response has been returned
   */
  req.getResponseHeader =
    function(headerName) {
      if (req.xmlHttpRequest!=null) {
        if (req.responseReceived) {
          return req.xmlHttpRequest.getResponseHeader(headerName);
        }
        alert("Cannot getResponseHeader because a response has not yet been received");
      }
    };

  return req;
}

// ---------------------------------------
// Static methods of the AjaxRequest class
// ---------------------------------------

/**
 * Returns an XMLHttpRequest object, either as a core object or an ActiveX 
 * implementation. If an object cannot be instantiated, it will return null;
 */
AjaxRequest.getXmlHttpRequest = function() 
{
	if (window.XMLHttpRequest) {
		return new XMLHttpRequest();
	}
	else if (window.ActiveXObject) {
		// Based on http://jibbering.com/2002/4/httprequest.html
		/*@cc_on @*/
		/*@if (@_jscript_version >= 5)
		try {
			return new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				return new ActiveXObject("Microsoft.XMLHTTP");
			} catch (E) {
				return null;
			}
		}
		@end @*/
	}
	else {
		return null;
	}
};

/**
 * See if any request is active in the background
 */
AjaxRequest.isActive = function() {
  return (AjaxRequest.numActiveAjaxRequests>0);
};

/**
 * Make a GET request. Pass an object containing parameters and arguments as 
 * the second argument.
 * These areguments may be either AjaxRequest properties to set on the request 
 * object or name/values to set in the request querystring.
 */
AjaxRequest.get = function(args) {
  AjaxRequest.doRequest("GET",args);
};

/**
 * Make a POST request. Pass an object containing parameters and arguments as 
 * the second argument.
 * These areguments may be either AjaxRequest properties to set on the request 
 * object or name/values to set in the request querystring.
 */
AjaxRequest.post = function(args) {
  AjaxRequest.doRequest("POST",args);
};

/**
 * The internal method used by the .get() and .post() methods
 */
AjaxRequest.doRequest = function(method,args) {
  if (defined(args) && args!=null) {
    var myRequest = new AjaxRequest();
    myRequest.method = method;
    myRequest.handleArguments(args);
    myRequest.process();
  }
};

/**
 * Submit a form. The requested URL will be the form's ACTION, and the request 
 * method will be the form's METHOD.
 * Returns true if the submittal was handled successfully, else false so it 
 * can easily be used with an onSubmit event for a form, and fallback to 
 * submitting the form normally.
 */
AjaxRequest.submit = function(theform, args) {
  var myRequest = new AjaxRequest();
  if (myRequest==null) { return false; }
  var serializedForm = AjaxRequest.serializeForm(theform);
  myRequest.method = theform.method.toUpperCase();
  myRequest.url = theform.action;
  myRequest.handleArguments(args);
  myRequest.queryString = serializedForm;
  myRequest.process();
  return true;
};

/**
 * Serialize a form into a format which can be sent as a GET string or a POST 
 * content.It correctly ignores disabled fields, maintains order of the fields 
 * as in the elements[] array. The 'file' input type is not supported, as 
 * its content is not available to javascript. This method is used internally
 * by the submit class method.
 */
AjaxRequest.serializeForm = function(theform) {
  var els = theform.elements;
  var len = els.length;
  var queryString = "";
  this.addField = 
    function(name,value) { 
      if (queryString.length>0) { 
        queryString += "&";
      }
      queryString += encodeURIComponent(name) + "=" + encodeURIComponent(value);
    };
  for (var i=0; i<len; i++) {
    var el = els[i];
    if (!el.disabled) {
      switch(el.type) {
        case 'text': case 'password': case 'hidden': case 'textarea': 
          this.addField(el.name,el.value);
          break;
        case 'select-one':
          if (el.selectedIndex>=0) {
            this.addField(el.name,el.options[el.selectedIndex].value);
          }
          break;
        case 'select-multiple':
          for (var j=0; j<el.options.length; j++) {
            if (el.options[j].selected) {
              this.addField(el.name,el.options[j].value);
            }
          }
          break;
        case 'checkbox': case 'radio':
          if (el.checked) {
            this.addField(el.name,el.value);
          }
          break;
      }
    }
  }
  return queryString;
};

// -----------------------
// Static Class variables
// -----------------------

/**
 * The number of total AjaxRequest objects currently active and running
 */
AjaxRequest.numActiveAjaxRequests = 0;

/**
 * An object holding the number of active requests for each group
 */
AjaxRequest.numActiveAjaxGroupRequests = new Object();

/**
 * The total number of AjaxRequest objects instantiated
 */
AjaxRequest.numAjaxRequests = 0;


