/*------------------------------------------------------------------------------
* Soft Idiom copyright 2007 Soft Idiom. All rights reserved.
*
* This module provides functions to make ajax calls.
* To use this object you call si4d_ajax.request with an options object. The
* options object can have the following properties:
*
*   url - the server url (this is mandatory).
*   method - the request methd and can be 'get' or 'post' (default is 'get').
*   asynchronous - if true then the request is asyncronous which means that 
*     the function does not wait for it to complete (default is false).
*   user - the user name for server authentication (optional).
*   password - the user password for server authentication (optional).
*   contentType - the request content type (default is 'text/plain').
*   encoding - the request encoding (default is 'UTF-8').
*   parameters - this is an object holding request parameters. If the method
*     is 'get' then these are added to the url and if the method is 'post'
*     then these are put in the request body.
*   body - if there are no parameters and the method is 'post' then this is
*     the request body. Note that you must set the correct contentType.
*   onload - a function that will be called when the request has completed.
*     This is optional but probably essential if the request is asynchronous.
*   onerror - a function that will be called if the request has an error.
*   timeout - the maximum time (in millseconds) to wait for an asynchronous
*     request to complete before doing an abort and raising an error (default
*     is 30 seconds).
*
* Features:
* - error handling interface
* - timeouts
* - Request & Response object interface
* - post parameters or data
*
* TODO
* - multiple simultaneous requests
*
* 1.00 02/05/2007
* - First version.
------------------------------------------------------------------------------*/


/*------------------------------------------------------------------------------
* si4d_ajax
------------------------------------------------------------------------------*/
var si4d_ajax = {
  version:              "1.00",   // current version number
  debug:                false,    // if true display debaug alert messages
  transport:            null,     // xmlhttp transport object
  Request:              null,     // the current request object
  Response:             null,     // the current response object
  
  MAXIMUM_WAITING_TIME: 30000,    // default request timeout
  
  /*****************************************************************************
  * Initialise the object.
  * Parameters:
  *   None.
  * Returns:
  *   None.
  ******************************************************************************/
  init: function() {
    this.transport = this._getTransport();
  },
  /*****************************************************************************
  * Make an Ajax request.
  * Parameters:
  *   options - the request options.
  * Returns:
  *   The response object.
  ******************************************************************************/
  request: function(options) {
    if(this.transport == null){ return; }
    // setup the request object using these defaults.
    this.Request = this._extend({
      method:       "GET",
      asynchronous: false,
      user:         null,
      password:     null,
      contentType:  "text/plain",
      encoding:     "UTF-8",
      parameters:   null,
      body:         null,
      onload:       null,
      onerror:      null,
      timeout:      this.MAXIMUM_WAITING_TIME
    }, options);
    this.Request.method = this.Request.method.toUpperCase();
    
    // initialise the xmlhttp object.
    this.transport.open(this.Request.method, this.Request.url, this.Request.asynchronous, this.Request.user, this.Request.password);
    
    // do any processing for the method type.
    if(this.Request.method == "GET"){
      // add any parameters to the url.
      if(this.Request.parameters != null){
        this.Request.url += (this.Request.url.indexOf("?") == 0) ? "?": "&";
        this.Request.url += this._urlEncodeParameters(this.Request.parameters);
      }
      this.Request.body = null;
    }else if(this.Request.method == "POST"){
      if(this.Request.parameters != null){
        // put the parameters in the body.
        this.transport.setRequestHeader('Content-Type', "application/x-www-form-urlencoded");
        this.Request.body = this._urlEncodeParameters(this.Request.parameters);
      }else{
        // the body is whatever is defined in the request.
        this.transport.setRequestHeader('Content-Type', this.Request.contentType);
      }
    }
    
    // setup the response object.
    this.Response = {
      status:   0,
      timeout:  null
    }
    
    // if asynchronus then setup state change & timeout event handlers.
    if(this.Request.asynchronous){
      this.transport.onreadystatechange = this._onStateChangeEvent;
      this.Response.timeout = setTimeout(this._onTimeoutEvent, this.Request.timeout);
    }
    
    // send the request.
    if(this.debug){ this._debugAlertMsg("ajax request:\n", this.Request); }
    this.transport.send(this.Request.body);
    
    // if synchronus then we have finished so set the response object properties.
    if(!this.Request.asynchronous){
      this.Response.status = this.transport.status;
      this.Response.statusText = this.transport.statusText;
      this.Response.responseText = this.transport.responseText;
      this.Response.responseXML = this.transport.responseXML;
      if(this.Response.status != 200 && this.Request.onerror != null){
        // call the onerror function with response object context.
        this.Response.onerror = this.Request.onerror;
        this.Response.onerror();
      }
      else if(this.Request.onload){
        // call the onload function with response object context.
        this.Response.onload = this.Request.onload;
        this.Response.onload();
      }
      if(this.debug){ this._debugAlertMsg("ajax synchronous response:\n", this.Response); }
    }
    
    return this.Response;
  },
  /*****************************************************************************
  * Set this object in test mode.
  * Parameters:
  *   testSequence - the test sequence object.
  *   testOptions - an optional object holding test options.
  * Returns:
  *   None.
  ******************************************************************************/
  setTestMode: function(testSequence, testOptions) {
    this.transport = new XMLHttpTest(testSequence, testOptions);
  },
  
  //============================================================================
  // The following functions are private to this object.
  
  /*****************************************************************************
  * Handle a state change event.
  * Note that the context of this function is the XMLHttpRequest object, not
  * this si4d_ajax object.
  * Parameters:
  *   None.
  * Returns:
  *   None.
  ******************************************************************************/
  _onStateChangeEvent: function() {
    switch(this.readyState){
    case 0:
      // 0 - Uninitialized
      return; 
    case 1:
      // 1 - Open
      return; 
    case 2:
      // 2 - Sent
      return; 
    case 3:
      // 3 - Receiving
      return; 
    case 4:
      // 4 - Loaded
      // stop the timeout.
      clearTimeout(si4d_ajax.Response.timeout);
      
      // setup the response object.
      si4d_ajax.Response.readyState = this.readyState;
      si4d_ajax.Response.status = this.status;
      si4d_ajax.Response.statusText = this.statusText;
      si4d_ajax.Response.responseText = this.responseText;
      si4d_ajax.Response.responseXML = this.responseXML;
      if(si4d_ajax.status != 200 && si4d_ajax.Request.onerror != null){
        // call the onerror function with response object context.
        si4d_ajax.Response.onerror = si4d_ajax.Request.onerror;
        si4d_ajax.Response.onerror();
      }
      else if(si4d_ajax.Request.onload != null){
        // call the onload function with response object context.
        si4d_ajax.Response.onload = si4d_ajax.Request.onload;
        si4d_ajax.Response.onload();
      }
      if(si4d_ajax.debug){ si4d_ajax._debugAlertMsg("onStateChangeEvent ajax response:\n", si4d_ajax.Response); }
      return; 
    }
  },
  /*****************************************************************************
  * Handle a timeout event.
  * This function only gets called if the request is asynchronous and the 
  * xmlhttp object has not reached 'Loaded' state before the timeout period has
  * expired.
  * Note that the context of this function is the document object, not this 
  * si4d_ajax object.
  * Parameters:
  *   None.
  * Returns:
  *   None.
  ******************************************************************************/
  _onTimeoutEvent: function() {
    if(si4d_ajax.transport == null){ return; }
    // check for a race condition.
    if(si4d_ajax.transport.readyState == 4){ return; };
    
    // abort the xmlhttp request.
    si4d_ajax.transport.abort();
    
    // setup the response object.
    si4d_ajax.Response.readyState = 0;
    si4d_ajax.Response.status = 500;
    si4d_ajax.Response.statusText = "ajax timeout";
    si4d_ajax.Response.responseText = null;
    si4d_ajax.Response.responseXML = null;
    if(si4d_ajax.Request.asynchronous){
      if(si4d_ajax.Request.onerror != null){
        // call the onerror function with response object context.
        si4d_ajax.Response.onerror = si4d_ajax.Request.onerror;
        si4d_ajax.Response.onerror();
      }
      if(si4d_ajax.debug){ si4d_ajax._debugAlertMsg("onTimeoutEvent ajax response:\n", si4d_ajax.Response); }
    }
  },
  /*****************************************************************************
  * Exentd an object by copying all the properties and function from the 
  * parent object.
  * Parameters:
  *   child - the object to extend.
  *   parent - the parent object.
  * Returns:
  *   The child object.
  ******************************************************************************/
  _extend: function(child, parent){
    for(var property in parent){
      child[property] = parent[property];
    }
    return child;
  },
  /*****************************************************************************
  * Return a string of url encoded parameters from the parameters supplied.
  * Parameters:
  *   parameters - the string or object.
  * Returns:
  *   The url encoded parameter string.
  ******************************************************************************/
  _urlEncodeParameters: function(parameters){
    // If the parameters are already a string then just return them.
    if(parameters == null){ return parameters; }
    if(typeof parameters == 'string'){ return parameters; }
    var parameterString = "";
    for(var property in parameters){
      if(parameterString.length > 0){ parameterString += "&"; }
      parameterString += property + "=" + escape(parameters[property]);
    }
    return parameterString;
  },
  /*****************************************************************************
  * Display a debug alert message.
  * Parameters:
  *   message - the message to be displayed.
  *   object - an optional object whose properties will be added to the message.
  * Returns:
  *   None.
  ******************************************************************************/
  _debugAlertMsg: function(message, object) {
    for(var property in object){
      message += property + " = '" + object[property] + "'\n";
    }
    alert(message);
  },
  /*****************************************************************************
  * Return the XMLHTTP transport object.
  * Parameters:
  *   None.
  * Returns:
  *   The XMLHTTP object or null if it can not be found..
  ******************************************************************************/
  _getTransport: function() {
    try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
    try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {}
    try { return new XMLHttpRequest(); } catch(e) {}
    //alert("can not create XMLHTTP object");
    return null;
  }
}

//-----------------------------------------------------------------------------
// Initialize the object.
si4d_ajax.init();


