/*
License: GNU Lesser General Public License (http://www.gnu.org/licenses/lgpl.html)
Copyright (C) 2005 tagnetic.org. Please do not remove this copyright/license comment.
*/
//*************************************************************************
//Start SvrReq <view:if><view:test>useSvrReq</view:test>
//*************************************************************************
/**
 * Class to make server requests via the XMLHTTPRequest object.
 * 
 * http://developer.apple.com/internet/webcontent/xmlhttpreq.html
 * served as a reference for the core of this class.
 * 
 * For XML responses, this object does NOT automatically call XMLHTTPRequest.overrideMimeType.
 * It is expected that the server is configured correctly to send the correct Content-type
 * mime type, particularly if a specific charset is desired. The instance method 
 * .overrideMimeType(mimeType) can be used if you want to force a particular mime type, but
 * it is not supported for MSIE. Make sure the HTTP server is configured correctly to return
 * the right Content-Type header.
 * 
 * If you need direct access to the underlying browser's implementation of
 * XMLHTTPRequest object, you can use the .impl member property of this object. For instance:
 *   var myrequest = new SvrReq();
 *   myrequest.impl.responseText //to access browser's implementation of XMLHTTPRequest
 *   
 * There seems to be an issue with MSIE (at least version 6.0 on XP SP2) where if you
 * use XMLHTTPRequest from a local file on disk to get XML from another local file on
 * disk. It doesn't seem to return a fully valid DOM. In that case, use the impl.responseText
 * and feed it to the DOMParser class defined in SDom.js.
 *
 * @class
 */
SvrReq = function()
  {
  this._id = 'id' + (SvrReq._idCounter++);
  this.impl = SvrReq.createReqObject();
  SvrReq._inst[this._id] = this;
  };

//*******************************************************************
//static (class) methods/properties
  
//*******************************************************************
/**
 * Override this value if you want to send a different
 * mime type (like wanting to add a charset).
 */
SvrReq.postMimeType = 'application/x-www-form-urlencoded';

//*******************************************************************
/**
 * You can call this method without using a SvrReq instance, if you
 * want total control over the request implementation, instead of
 * using a SvrReq instance.
 * 
 * This method may throw exceptions.
 */
SvrReq.createReqObject = function()
  {
  var req = null;
  if(window.XMLHttpRequest)
    req = new XMLHttpRequest();
  else if(window.ActiveXObject)
    {
    try
      {
      req = new ActiveXObject("Msxml2.XMLHTTP");
      }
    catch(e)
      {
      req = new ActiveXObject("Microsoft.XMLHTTP");
      }
    }
  return req;
  }

//*******************************************************************
/**
 * @private variable to hold onto SvrReq instances (used to 
 * route request callbacks).
 */
SvrReq._inst = new Object();

//*******************************************************************
/**
 * @private method used to get callbacks from the underlying request object.
 * 
 */
SvrReq._forwardStateChange = function(id)
  {
  SvrReq._inst[id]._stateChange();
  }

//*******************************************************************
SvrReq._idCounter = 1;

//*******************************************************************
/**
 * Instance methods/properties.
 */
SvrReq.prototype =
  {
  //*******************************************************************
  /**
   * Use this instance method to force a particular mime type for the
   * response data. Call this method before calling send().
   * This method does nothing for MSIE browsers, since it does not support
   * the method on their XMLHTTPRequest implementation.
   * Ideally this method would not be needed if the HTTP server always sends
   * the correct mime type in the HTTP headers (with the appropriate charset
   * encoding). You should make sure the HTTP server is configured correctly 
   * before using this method.
   */
  overrideMimeType: function(mimeType)
    {
    this._mimeType = mimeType;
    },

  //*******************************************************************
  /**
   * Adds a script src to a page.
   * 
   * @param {String} url: The URL for the request.
   * 
   * @param {String} type: Optional.
   * Type of request. The following values end up with the associated object
   * being passed back to the onOk method:
   *   - 'xml': the responseXML (should be a DOM object).
   *   - 'js': the responseText is passed to eval(), and the result of 
   *     eval() is passed to onOk.
   *   - any other value (including null): the responseText is passed as 
   *     the first argument to onOk.
   * 
   * @param {Object} listener: An object that contains function callbacks 
   * for the different events:
   *   - onOk(result): Callback function when request successfully completes.
   *     See info about the 'type' parameter above for the format of the result object.
   *   - onError(error): Optional. Recommended. Callback function when an error occurs.
   *     The error/exception will be passed as the only parameter to this function.
   *   - onTimeout(): Callback function when request takes longer than timeout specified in
         next parameter.
   *   - timeout: integer as number of seconds to wait until timeout.
   * 
   * Example listener object:
   * 
   * var listener = { 
   *                onOk: function() { alert('loaded'); },
   *                onError: function(requestStatus, error) { alert('error: ' + error); },
   *                onTimeout: function() { alert('timeout'); },
   *                timeout: 30
   *                };
   *                
   *  @param {String} postData: Optional.Data to be sent via an HTTP
   *  POST. If it is specified, then HTTP POST will be used for the request.
   *  If it is not, HTTP GET will be used. Other HTTP methods are not supported.
   *  For other HTTP methods, use the static method SvrReq.createReqObject()
   *  to get a plain XMLHTTPRequest object, and configure it as you need to.
   *  
   *  The format of the postData should be the normal url encoded form:
   *  name=value&name=value&name=value
   *  
   *  @param {Object} headers: Optional. An object that has properties
   *  that are HTTP header names, and values that correspond to the 
   *  values for the HTTP header names. Example:
   *  
   *  var headers = {
   *                'Content-Type': 'text/foo',
   *                'X-MySpecialHeader': 'bar'
   *                };
   */
  send: function(url, type, listener, postData, headers)
    {
    var req = this.impl;
    
    this._type = type;
    this._listener = listener;
    
    //Set request headers.
    if (postData)
      req.setRequestHeader('Content-Type', SvrReq.postMimeType);
    if (headers)
      {
      for (var param in headers)
        req.setRequestHeader(param, headers[param]);
      }

    var stateChangeFunction;
    eval ('stateChangeFunction = function() { SvrReq._forwardStateChange("' + this._id + '"); }');
    req.onreadystatechange = stateChangeFunction;
    
    //req.onreadystatechange = function() {SvrReq._forwardStateChange(this._id);};
    req.open(postData ? 'POST' : 'GET', url, true);
      
    if (this._mimeType && typeof(this.impl.overrideMimeType) != 'undefined')
      this.impl.overrideMimeType(this._mimeType);
      
    if (this._listener.onTimeout && this._listener.timeout)
      {
      var timeoutFunction;
      eval('timeoutFunction = function() { SvrReq._inst["' + this._id + '"]._timeout(); }');
      this._timeoutId = setTimeout(timeoutFunction, (this._listener.timeout * 1000));
      }
    req.send(postData);
    },
 
  //*******************************************************************
  /**
   * Use this instance method to cancel a request that has already been
   * sent, but before a response is received. If this method is called,
   * no callback handler will be called on the request.
   */
  abort: function()
    {
    this._clearTimeout();
    this.impl.abort();
    },

  //*******************************************************************
  _ok: function()
    {
    this._clearTimeout();
    var result = (this._type == 'xml' ? this.impl.responseXML : this.impl.responseText);
    if (this._type == 'js')
      result = eval(result);
    this._listener.onOk(result);
    },
    
  //*******************************************************************
  _error: function(error)
    {
    this._clearTimeout();
    if (this._listener.onError)
      this._listener.onError(this.impl.status, error);
    else
      throw error;
    },
    
  //*******************************************************************
  _timeout: function()
    {
    this._timeoutId  = 0;
    this.abort();
    this._listener.onTimeout();
    },

  //*******************************************************************
  _clearTimeout: function()
    {
    if (this._timeoutId)
      {
      clearTimeout(this._timeoutId);
      this._timeoutId  = 0;
      }
    },

  //*******************************************************************
  _stateChange: function()
    {
    var req = this.impl;
    if (req.readyState == 4)
      {
      //0 sent for cached responses? The req.status undefined is for Safari 2.0.1,
      //which seems to return that for local file requests.
      if (req.status == 200 || req.status == 0 || (typeof(req.status == 'undefined' && location.protocol == 'file:')))
        {
        try
          {
          this._ok();
          }
        catch (exception)
          {
          this._error(exception);
          }
        }
      else
        this._error('Invalid request status: ' + req.status);
      }
    }
  }
//*************************************************************************
//End SvrReq </view:if>
//*************************************************************************

