
/**
 * Ajax pattern implementation providing interface to handle asynchronous
 * xml http requests using a single object instance. 
 * 
 * @author David Stefan
 * @version 1.0.0
 */
function Ajax()
{
  // Instance variables.
  this.xmlHttp = Array();
  
  // Class methods.
  this.getXmlHttp = getXmlHttp;  
  this.update = update;
  this.updateGet = updateGet;
  this.updatePost = updatePost;  
  this.readyStateChange = readyStateChange;
  this.serialize = serialize;
  
  // Ajax object events
  this.onloading = null;
  this.onloaded = null;
  this.oninteractive = null;
  this.oncomplete = null;

  /**
   * Update method requires callerId and scriptUrl arguments. The latter two
   * are optional. First argument determines which object will be used for
   * xmlHttp request, scriptUrl specifies target script.
   *
   * @param callerId, scriptUrl [, data [, optArray]]
   */
  function update(callerId, scriptUrl, data, optArray)
  {
    this.currentCallerId = callerId;
    this.optArray = optArray;
  
    if(this.xmlHttp[callerId] && this.xmlHttp[callerId].readyState != 4)
    {
      this.xmlHttp[callerId].abort();
    }
    this.xmlHttp[callerId] = this.getXmlHttp();
    
    var _this = this;
    this.xmlHttp[callerId].onreadystatechange = function()
    {
      //alert(_this.xmlHttp[callerId].responseText);
      _this.readyStateChange(_this.xmlHttp[callerId], optArray);
    }

    // If optArray defines 'method' element, xmlHttp method open is called
    // width corresponding method argument.
    if(optArray && optArray["method"])
    {
      switch(optArray["method"].toUpperCase())
      {
        case "GET": {
          this.updateGet(this.xmlHttp[callerId], scriptUrl, data, optArray);
          break;
        }
        case "POST": {
          this.updatePost(this.xmlHttp[callerId], scriptUrl, data, optArray);
          break;
        }
      }
    }
    else
    {
      this.updateGet(this.xmlHttp[callerId], scriptUrl, data, optArray);
    }
  }

  function updateGet(xmlHttp, scriptUrl, data, optArray)
  {
    scriptUrl += "?" + data;
    xmlHttp.open("GET", scriptUrl, true);
    xmlHttp.send(data);
  }
  
  function updatePost(xmlHttp, scriptUrl, data, optArray)
  {
    xmlHttp.open("POST", scriptUrl, true);
    xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlHttp.setRequestHeader("Content-length", data.length);
    xmlHttp.setRequestHeader("Connection", "close");
    xmlHttp.send(data);
  }
  
  function serialize(formName)
  {
    var form = document.forms[formName];
    var query = "";
    
    for(i = 0; i < form.length; i++)
    {
      query += encodeURI(form[i].name) + "=" + encodeURI(form[i].value) + "&";
    }
    return query;
  }

  /**
   * To be assigned to xmlHttp object onreadystatechange event. It takes
   * xmlHttp object and optArray as its arguments to pass those forward onto
   * Ajax event-triggered functions.
   *
   * @param xmlHttpObject, Array
   */
  function readyStateChange(xmlHttp, optArray)
  {
    switch(xmlHttp.readyState)
    {
      case 1: {
        this.onloading ? this.onloading(xmlHttp, optArray) : null;
        break;
      }
      case 2: {
        this.onloaded ? this.onloaded(xmlHttp, optArray) : null;
        break;
      }
      case 3: {
        this.oninteractive ? this.oninteractive(xmlHttp, optArray) : null;
        break;
      }
      case 4: {
        //alert(xmlHttp.responseText);
        this.oncomplete ? this.oncomplete(xmlHttp, optArray) : null;
        break;
      }
    }
  }

  /**
   * Returns a new xmlHttp object.
   */
  function getXmlHttp()
  {
    var xmlHttp = null;
    
    try
    {
      // Firefox, Opera, Safari.
      xmlHttp = new XMLHttpRequest();
    }
    catch(e)
    {
      try
      {
        // Internet Explorer.
        xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
      }
      catch(e)
      {
        try
        {
          xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch(e)
        {
          xmlHttp = null;
        }
      }
    }
    return xmlHttp;
  }
}
